From 9a5079d4ecc0f6b4185afff3e738825f1e3d29c0 Mon Sep 17 00:00:00 2001 From: "honfika@gmail.com" Date: Sat, 4 Jul 2015 21:00:41 +0200 Subject: [PATCH] Introduce end-of-line normalization --- .../decompiler/flash/abc/ABCOutputStream.java | 612 +-- .../avm2/instructions/DeobfuscatePopIns.java | 90 +- .../abc/avm2/parser/pcode/ASM3Parser.java | 1980 ++++----- .../flash/abc/types/ABCException.java | 156 +- .../flash/tags/DefineEditTextTag.java | 2292 +++++----- .../console/CommandLineArgumentParser.java | 3874 ++++++++--------- 6 files changed, 4502 insertions(+), 4502 deletions(-) diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/ABCOutputStream.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/ABCOutputStream.java index a37781707..698793679 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/ABCOutputStream.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/ABCOutputStream.java @@ -1,306 +1,306 @@ -/* - * Copyright (C) 2010-2015 JPEXS, All rights reserved. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3.0 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. - */ -package com.jpexs.decompiler.flash.abc; - -import com.jpexs.decompiler.flash.abc.types.Decimal; -import com.jpexs.decompiler.flash.abc.types.InstanceInfo; -import com.jpexs.decompiler.flash.abc.types.MethodInfo; -import com.jpexs.decompiler.flash.abc.types.Multiname; -import com.jpexs.decompiler.flash.abc.types.Namespace; -import com.jpexs.decompiler.flash.abc.types.traits.Trait; -import com.jpexs.decompiler.flash.abc.types.traits.TraitClass; -import com.jpexs.decompiler.flash.abc.types.traits.TraitFunction; -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.helpers.utf8.Utf8Helper; -import java.io.IOException; -import java.io.OutputStream; - -public class ABCOutputStream extends OutputStream { - - private final OutputStream os; - - public ABCOutputStream(OutputStream os) { - this.os = os; - } - - @Override - public void write(int b) throws IOException { - os.write(b); - } - - public void writeU30(long value) throws IOException { - writeS32(value); - /*boolean loop = true; - boolean underZero=value<0; - - if(underZero){ - value = value & 0xFFFFFFFF; - }else{ - value = value & 0x7FFFFFFF; - } - do { - int ret = (int) (value & 0x7F); - if (value < 0x80) { - loop = false; - } - if (value > 0x7F) { - ret += 0x80; - } - write(ret); - value = value >> 7; - } while (loop); - */ - } - - public void writeU32(long value) throws IOException { - boolean loop = true; - value &= 0xFFFFFFFF; - do { - int ret = (int) (value & 0x7F); - if (value < 0x80) { - loop = false; - } - if (value > 0x7F) { - ret += 0x80; - } - write(ret); - value >>= 7; - } while (loop); - } - - public void writeS24(long value) throws IOException { - int ret = (int) (value & 0xff); - write(ret); - value >>= 8; - ret = (int) (value & 0xff); - write(ret); - value >>= 8; - ret = (int) (value & 0xff); - write(ret); - } - - public void writeS32(long value) throws IOException { - boolean belowZero = value < 0; - /*if (belowZero) { - value = -value; - }*/ - int bitcount = 0; - boolean loop = true; - //value = value & 0xFFFFFFFF; - do { - bitcount += 7; - int ret = (int) (value & 0x7F); - if (value < 0x80) { - if (belowZero) { //&& bitcount < 35 - ret += 0x80; - } else { - loop = false; - } - } else { - ret += 0x80; - } - - if (bitcount == 35) { - ret &= 0xf; - } - write(ret); - if (bitcount == 35) { - break; - } - value >>= 7; - } while (loop); - } - - public void writeLong(long value) throws IOException { - byte[] writeBuffer = new byte[8]; - writeBuffer[7] = (byte) (value >>> 56); - writeBuffer[6] = (byte) (value >>> 48); - writeBuffer[5] = (byte) (value >>> 40); - writeBuffer[4] = (byte) (value >>> 32); - writeBuffer[3] = (byte) (value >>> 24); - writeBuffer[2] = (byte) (value >>> 16); - writeBuffer[1] = (byte) (value >>> 8); - writeBuffer[0] = (byte) (value); - write(writeBuffer); - } - - public void writeDouble(double value) throws IOException { - writeLong(Double.doubleToLongBits(value)); - } - - public void writeU8(int value) throws IOException { - write(value); - } - - public void writeU16(int value) throws IOException { - write(value & 0xff); - write((value >> 8) & 0xff); - } - - public void writeString(String s) throws IOException { - byte[] sbytes = Utf8Helper.getBytes(s); - writeU30(sbytes.length); - write(sbytes); - } - - public void writeNamespace(Namespace ns) throws IOException { - write(ns.kind); - boolean found = false; - for (int k = 0; k < Namespace.nameSpaceKinds.length; k++) { - if (Namespace.nameSpaceKinds[k] == ns.kind) { - writeU30(ns.name_index); - found = true; - break; - } - } - if (!found) { - throw new RuntimeException("Invalid ns kind:" + ns.kind); - } - } - - public void writeMultiname(Multiname m) throws IOException { - writeU8(m.kind); - if ((m.kind == 7) || (m.kind == 0xd)) { // CONSTANT_QName and CONSTANT_QNameA. - writeU30(m.namespace_index); - writeU30(m.name_index); - } - if ((m.kind == 9) || (m.kind == 0xe)) { // CONSTANT_Multiname and CONSTANT_MultinameA. - writeU30(m.name_index); - writeU30(m.namespace_set_index); - } - if ((m.kind == 0xf) || (m.kind == 0x10)) { // CONSTANT_RTQName and CONSTANT_RTQNameA - writeU30(m.name_index); - } - if ((m.kind == 0x1B) || (m.kind == 0x1C)) { // CONSTANT_MultinameL and CONSTANT_MultinameLA - writeU30(m.namespace_set_index); - } - if (m.kind == 0x1D) { - writeU30(m.qname_index); - writeU30(m.params.size()); - for (int i = 0; i < m.params.size(); i++) { - writeU30(m.params.get(i)); - } - } - // kind==0x11,0x12 nothing CONSTANT_RTQNameL and CONSTANT_RTQNameLA. - } - - public void writeMethodInfo(MethodInfo mi) throws IOException { - writeU30(mi.param_types.length); - writeU30(mi.ret_type); - for (int i = 0; i < mi.param_types.length; i++) { - writeU30(mi.param_types[i]); - } - writeU30(mi.name_index); - write(mi.flags); - if ((mi.flags & 8) == 8) { - writeU30(mi.optional.length); - for (int i = 0; i < mi.optional.length; i++) { - writeU30(mi.optional[i].value_index); - write(mi.optional[i].value_kind); - } - } - - if ((mi.flags & 128) == 128) { // if has_paramnames - for (int i = 0; i < mi.paramNames.length; i++) { - writeU30(mi.paramNames[i]); - } - } - } - - public void writeTrait(Trait t) throws IOException { - writeU30(t.name_index); - write((t.kindFlags << 4) + t.kindType); - if (t instanceof TraitSlotConst) { - TraitSlotConst t1 = (TraitSlotConst) t; - writeU30(t1.slot_id); - writeU30(t1.type_index); - writeU30(t1.value_index); - if (t1.value_index != 0) { - write(t1.value_kind); - } - } - if (t instanceof TraitMethodGetterSetter) { - TraitMethodGetterSetter t2 = (TraitMethodGetterSetter) t; - writeU30(t2.disp_id); - writeU30(t2.method_info); - } - if (t instanceof TraitClass) { - TraitClass t3 = (TraitClass) t; - writeU30(t3.slot_id); - writeU30(t3.class_info); - } - if (t instanceof TraitFunction) { - TraitFunction t4 = (TraitFunction) t; - writeU30(t4.slot_id); - writeU30(t4.method_info); - } - if ((t.kindFlags & 4) == 4) { - writeU30(t.metadata.length); - for (int i = 0; i < t.metadata.length; i++) { - writeU30(t.metadata[i]); - } - } - } - - public void writeTraits(Traits t) throws IOException { - writeU30(t.traits.size()); - for (int i = 0; i < t.traits.size(); i++) { - writeTrait(t.traits.get(i)); - } - } - - public void writeInstanceInfo(InstanceInfo ii) throws IOException { - writeU30(ii.name_index); - writeU30(ii.super_index); - write(ii.flags); - if ((ii.flags & 8) == 8) { - writeU30(ii.protectedNS); - } - writeU30(ii.interfaces.length); - for (int i = 0; i < ii.interfaces.length; i++) { - writeU30(ii.interfaces[i]); - } - writeU30(ii.iinit_index); - writeTraits(ii.instance_traits); - } - - public void writeDecimal(Decimal value) throws IOException { - write(value.data); - } - - public static int getU30ByteLength(long value) { - boolean belowZero = value < 0; - int bitcount = 0; - int result = 0; - boolean loop = true; - do { - bitcount += 7; - if (value < 0x80 && !belowZero) { - loop = false; - } - - result++; - if (bitcount == 35) { - break; - } - value >>= 7; - } while (loop); - return result; - } -} +/* + * Copyright (C) 2010-2015 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.flash.abc; + +import com.jpexs.decompiler.flash.abc.types.Decimal; +import com.jpexs.decompiler.flash.abc.types.InstanceInfo; +import com.jpexs.decompiler.flash.abc.types.MethodInfo; +import com.jpexs.decompiler.flash.abc.types.Multiname; +import com.jpexs.decompiler.flash.abc.types.Namespace; +import com.jpexs.decompiler.flash.abc.types.traits.Trait; +import com.jpexs.decompiler.flash.abc.types.traits.TraitClass; +import com.jpexs.decompiler.flash.abc.types.traits.TraitFunction; +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.helpers.utf8.Utf8Helper; +import java.io.IOException; +import java.io.OutputStream; + +public class ABCOutputStream extends OutputStream { + + private final OutputStream os; + + public ABCOutputStream(OutputStream os) { + this.os = os; + } + + @Override + public void write(int b) throws IOException { + os.write(b); + } + + public void writeU30(long value) throws IOException { + writeS32(value); + /*boolean loop = true; + boolean underZero=value<0; + + if(underZero){ + value = value & 0xFFFFFFFF; + }else{ + value = value & 0x7FFFFFFF; + } + do { + int ret = (int) (value & 0x7F); + if (value < 0x80) { + loop = false; + } + if (value > 0x7F) { + ret += 0x80; + } + write(ret); + value = value >> 7; + } while (loop); + */ + } + + public void writeU32(long value) throws IOException { + boolean loop = true; + value &= 0xFFFFFFFF; + do { + int ret = (int) (value & 0x7F); + if (value < 0x80) { + loop = false; + } + if (value > 0x7F) { + ret += 0x80; + } + write(ret); + value >>= 7; + } while (loop); + } + + public void writeS24(long value) throws IOException { + int ret = (int) (value & 0xff); + write(ret); + value >>= 8; + ret = (int) (value & 0xff); + write(ret); + value >>= 8; + ret = (int) (value & 0xff); + write(ret); + } + + public void writeS32(long value) throws IOException { + boolean belowZero = value < 0; + /*if (belowZero) { + value = -value; + }*/ + int bitcount = 0; + boolean loop = true; + //value = value & 0xFFFFFFFF; + do { + bitcount += 7; + int ret = (int) (value & 0x7F); + if (value < 0x80) { + if (belowZero) { //&& bitcount < 35 + ret += 0x80; + } else { + loop = false; + } + } else { + ret += 0x80; + } + + if (bitcount == 35) { + ret &= 0xf; + } + write(ret); + if (bitcount == 35) { + break; + } + value >>= 7; + } while (loop); + } + + public void writeLong(long value) throws IOException { + byte[] writeBuffer = new byte[8]; + writeBuffer[7] = (byte) (value >>> 56); + writeBuffer[6] = (byte) (value >>> 48); + writeBuffer[5] = (byte) (value >>> 40); + writeBuffer[4] = (byte) (value >>> 32); + writeBuffer[3] = (byte) (value >>> 24); + writeBuffer[2] = (byte) (value >>> 16); + writeBuffer[1] = (byte) (value >>> 8); + writeBuffer[0] = (byte) (value); + write(writeBuffer); + } + + public void writeDouble(double value) throws IOException { + writeLong(Double.doubleToLongBits(value)); + } + + public void writeU8(int value) throws IOException { + write(value); + } + + public void writeU16(int value) throws IOException { + write(value & 0xff); + write((value >> 8) & 0xff); + } + + public void writeString(String s) throws IOException { + byte[] sbytes = Utf8Helper.getBytes(s); + writeU30(sbytes.length); + write(sbytes); + } + + public void writeNamespace(Namespace ns) throws IOException { + write(ns.kind); + boolean found = false; + for (int k = 0; k < Namespace.nameSpaceKinds.length; k++) { + if (Namespace.nameSpaceKinds[k] == ns.kind) { + writeU30(ns.name_index); + found = true; + break; + } + } + if (!found) { + throw new RuntimeException("Invalid ns kind:" + ns.kind); + } + } + + public void writeMultiname(Multiname m) throws IOException { + writeU8(m.kind); + if ((m.kind == 7) || (m.kind == 0xd)) { // CONSTANT_QName and CONSTANT_QNameA. + writeU30(m.namespace_index); + writeU30(m.name_index); + } + if ((m.kind == 9) || (m.kind == 0xe)) { // CONSTANT_Multiname and CONSTANT_MultinameA. + writeU30(m.name_index); + writeU30(m.namespace_set_index); + } + if ((m.kind == 0xf) || (m.kind == 0x10)) { // CONSTANT_RTQName and CONSTANT_RTQNameA + writeU30(m.name_index); + } + if ((m.kind == 0x1B) || (m.kind == 0x1C)) { // CONSTANT_MultinameL and CONSTANT_MultinameLA + writeU30(m.namespace_set_index); + } + if (m.kind == 0x1D) { + writeU30(m.qname_index); + writeU30(m.params.size()); + for (int i = 0; i < m.params.size(); i++) { + writeU30(m.params.get(i)); + } + } + // kind==0x11,0x12 nothing CONSTANT_RTQNameL and CONSTANT_RTQNameLA. + } + + public void writeMethodInfo(MethodInfo mi) throws IOException { + writeU30(mi.param_types.length); + writeU30(mi.ret_type); + for (int i = 0; i < mi.param_types.length; i++) { + writeU30(mi.param_types[i]); + } + writeU30(mi.name_index); + write(mi.flags); + if ((mi.flags & 8) == 8) { + writeU30(mi.optional.length); + for (int i = 0; i < mi.optional.length; i++) { + writeU30(mi.optional[i].value_index); + write(mi.optional[i].value_kind); + } + } + + if ((mi.flags & 128) == 128) { // if has_paramnames + for (int i = 0; i < mi.paramNames.length; i++) { + writeU30(mi.paramNames[i]); + } + } + } + + public void writeTrait(Trait t) throws IOException { + writeU30(t.name_index); + write((t.kindFlags << 4) + t.kindType); + if (t instanceof TraitSlotConst) { + TraitSlotConst t1 = (TraitSlotConst) t; + writeU30(t1.slot_id); + writeU30(t1.type_index); + writeU30(t1.value_index); + if (t1.value_index != 0) { + write(t1.value_kind); + } + } + if (t instanceof TraitMethodGetterSetter) { + TraitMethodGetterSetter t2 = (TraitMethodGetterSetter) t; + writeU30(t2.disp_id); + writeU30(t2.method_info); + } + if (t instanceof TraitClass) { + TraitClass t3 = (TraitClass) t; + writeU30(t3.slot_id); + writeU30(t3.class_info); + } + if (t instanceof TraitFunction) { + TraitFunction t4 = (TraitFunction) t; + writeU30(t4.slot_id); + writeU30(t4.method_info); + } + if ((t.kindFlags & 4) == 4) { + writeU30(t.metadata.length); + for (int i = 0; i < t.metadata.length; i++) { + writeU30(t.metadata[i]); + } + } + } + + public void writeTraits(Traits t) throws IOException { + writeU30(t.traits.size()); + for (int i = 0; i < t.traits.size(); i++) { + writeTrait(t.traits.get(i)); + } + } + + public void writeInstanceInfo(InstanceInfo ii) throws IOException { + writeU30(ii.name_index); + writeU30(ii.super_index); + write(ii.flags); + if ((ii.flags & 8) == 8) { + writeU30(ii.protectedNS); + } + writeU30(ii.interfaces.length); + for (int i = 0; i < ii.interfaces.length; i++) { + writeU30(ii.interfaces[i]); + } + writeU30(ii.iinit_index); + writeTraits(ii.instance_traits); + } + + public void writeDecimal(Decimal value) throws IOException { + write(value.data); + } + + public static int getU30ByteLength(long value) { + boolean belowZero = value < 0; + int bitcount = 0; + int result = 0; + boolean loop = true; + do { + bitcount += 7; + if (value < 0x80 && !belowZero) { + loop = false; + } + + result++; + if (bitcount == 35) { + break; + } + value >>= 7; + } while (loop); + return result; + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/instructions/DeobfuscatePopIns.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/instructions/DeobfuscatePopIns.java index 3773d0f9e..a27c2c87f 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/instructions/DeobfuscatePopIns.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/instructions/DeobfuscatePopIns.java @@ -1,45 +1,45 @@ -/* - * Copyright (C) 2010-2015 JPEXS, All rights reserved. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3.0 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. - */ -package com.jpexs.decompiler.flash.abc.avm2.instructions; - -import com.jpexs.decompiler.flash.abc.ABC; -import com.jpexs.decompiler.flash.abc.avm2.AVM2Code; -import com.jpexs.decompiler.flash.abc.avm2.AVM2ConstantPool; -import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.PopIns; -import com.jpexs.decompiler.flash.abc.types.MethodBody; -import com.jpexs.decompiler.flash.abc.types.MethodInfo; -import com.jpexs.decompiler.graph.GraphTargetItem; -import com.jpexs.decompiler.graph.ScopeStack; -import com.jpexs.decompiler.graph.TranslateStack; -import java.util.HashMap; -import java.util.List; - -/** - * - * @author JPEXS - */ -public class DeobfuscatePopIns extends PopIns { - - public DeobfuscatePopIns() { - instructionName = "ffdec_deobfuscatepop"; - } - - @Override - public void translate(boolean isStatic, int scriptIndex, int classIndex, HashMap localRegs, TranslateStack stack, ScopeStack scopeStack, AVM2ConstantPool constants, AVM2Instruction ins, List method_info, List output, MethodBody body, ABC abc, HashMap localRegNames, List fullyQualifiedNames, String path, HashMap localRegsAssignmentIps, int ip, HashMap> refs, AVM2Code code) { - stack.pop(); //Just ignore the value - } -} +/* + * Copyright (C) 2010-2015 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.flash.abc.avm2.instructions; + +import com.jpexs.decompiler.flash.abc.ABC; +import com.jpexs.decompiler.flash.abc.avm2.AVM2Code; +import com.jpexs.decompiler.flash.abc.avm2.AVM2ConstantPool; +import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.PopIns; +import com.jpexs.decompiler.flash.abc.types.MethodBody; +import com.jpexs.decompiler.flash.abc.types.MethodInfo; +import com.jpexs.decompiler.graph.GraphTargetItem; +import com.jpexs.decompiler.graph.ScopeStack; +import com.jpexs.decompiler.graph.TranslateStack; +import java.util.HashMap; +import java.util.List; + +/** + * + * @author JPEXS + */ +public class DeobfuscatePopIns extends PopIns { + + public DeobfuscatePopIns() { + instructionName = "ffdec_deobfuscatepop"; + } + + @Override + public void translate(boolean isStatic, int scriptIndex, int classIndex, HashMap localRegs, TranslateStack stack, ScopeStack scopeStack, AVM2ConstantPool constants, AVM2Instruction ins, List method_info, List output, MethodBody body, ABC abc, HashMap localRegNames, List fullyQualifiedNames, String path, HashMap localRegsAssignmentIps, int ip, HashMap> refs, AVM2Code code) { + stack.pop(); //Just ignore the value + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/parser/pcode/ASM3Parser.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/parser/pcode/ASM3Parser.java index 8d718c00b..169a26f4b 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/parser/pcode/ASM3Parser.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/parser/pcode/ASM3Parser.java @@ -1,990 +1,990 @@ -/* - * Copyright (C) 2010-2015 JPEXS, All rights reserved. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3.0 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. - */ -package com.jpexs.decompiler.flash.abc.avm2.parser.pcode; - -import com.jpexs.decompiler.flash.abc.avm2.AVM2Code; -import com.jpexs.decompiler.flash.abc.avm2.AVM2ConstantPool; -import com.jpexs.decompiler.flash.abc.avm2.instructions.AVM2Instruction; -import com.jpexs.decompiler.flash.abc.avm2.instructions.DeobfuscatePopIns; -import com.jpexs.decompiler.flash.abc.avm2.instructions.InstructionDefinition; -import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.PushShortIns; -import com.jpexs.decompiler.flash.abc.avm2.parser.AVM2ParseException; -import com.jpexs.decompiler.flash.abc.types.ABCException; -import com.jpexs.decompiler.flash.abc.types.MethodBody; -import com.jpexs.decompiler.flash.abc.types.MethodInfo; -import com.jpexs.decompiler.flash.abc.types.Multiname; -import com.jpexs.decompiler.flash.abc.types.Namespace; -import com.jpexs.decompiler.flash.abc.types.NamespaceSet; -import com.jpexs.decompiler.flash.abc.types.ValueKind; -import com.jpexs.decompiler.flash.abc.types.traits.Trait; -import com.jpexs.decompiler.flash.abc.types.traits.TraitFunction; -import com.jpexs.decompiler.flash.abc.types.traits.TraitMethodGetterSetter; -import com.jpexs.decompiler.flash.abc.types.traits.TraitSlotConst; -import com.jpexs.decompiler.flash.configuration.Configuration; -import java.io.IOException; -import java.io.Reader; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Locale; - -public class ASM3Parser { - - private static class OffsetItem { - - public String label = ""; - - public long insPosition; - - public int insOperandIndex; - - public OffsetItem(String label, long insOffset, int insOperandIndex) { - this.label = label; - this.insPosition = insOffset; - this.insOperandIndex = insOperandIndex; - } - } - - private static class CaseOffsetItem extends OffsetItem { - - public CaseOffsetItem(String label, long insOffset, int insOperandIndex) { - super(label, insOffset, insOperandIndex); - } - } - - private static class LabelItem { - - public String label = ""; - - public int offset; - - public LabelItem(String label, int offset) { - this.label = label; - this.offset = offset; - } - } - - public static AVM2Code parse(Reader reader, AVM2ConstantPool constants, Trait trait, MethodBody body, MethodInfo info) throws IOException, AVM2ParseException, InterruptedException { - return parse(reader, constants, trait, null, body, info); - } - - private static int checkMultinameIndex(AVM2ConstantPool constants, int index, int line) throws AVM2ParseException { - if ((index < 0) || (index >= constants.getMultinameCount())) { - throw new AVM2ParseException("Invalid multiname index", line); - } - return index; - } - - private static void expected(int type, String expStr, Flasm3Lexer lexer) throws IOException, AVM2ParseException { - ParsedSymbol s = lexer.lex(); - if (s.type != type) { - throw new AVM2ParseException(expStr + " expected", lexer.yyline()); - } - } - - private static void expected(ParsedSymbol s, int type, String expStr) throws IOException, AVM2ParseException { - if (s.type != type) { - throw new AVM2ParseException(expStr + " expected", 0); - } - } - - public static boolean parseSlotConst(Reader reader, AVM2ConstantPool constants, TraitSlotConst tsc) throws IOException, AVM2ParseException { - Flasm3Lexer lexer = new Flasm3Lexer(reader); - expected(ParsedSymbol.TYPE_KEYWORD_TRAIT, "trait", lexer); - int name_index = parseMultiName(constants, lexer); - - ParsedSymbol symb = lexer.lex(); - - int flags = 0; - while (symb.type == ParsedSymbol.TYPE_KEYWORD_FLAG) { - symb = lexer.lex(); - switch (symb.type) { - case ParsedSymbol.TYPE_KEYWORD_FINAL: - flags |= Trait.ATTR_Final; - break; - case ParsedSymbol.TYPE_KEYWORD_OVERRIDE: - flags |= Trait.ATTR_Override; - break; - case ParsedSymbol.TYPE_KEYWORD_METADATA: - flags |= Trait.ATTR_Metadata; - break; - default: - throw new AVM2ParseException("Invalid trait flag", lexer.yyline()); - } - symb = lexer.lex(); - } - - switch (symb.type) { - case ParsedSymbol.TYPE_KEYWORD_SLOT: - case ParsedSymbol.TYPE_KEYWORD_CONST: - expected(ParsedSymbol.TYPE_KEYWORD_SLOTID, "slotid", lexer); - symb = lexer.lex(); - expected(symb, ParsedSymbol.TYPE_INTEGER, "Integer"); - int slotid = (int) (long) (Long) symb.value; - expected(ParsedSymbol.TYPE_KEYWORD_TYPE, "type", lexer); - int type = parseMultiName(constants, lexer); - expected(ParsedSymbol.TYPE_KEYWORD_VALUE, "value", lexer); - ValueKind val = parseValue(constants, lexer); - tsc.slot_id = slotid; - tsc.type_index = type; - tsc.value_kind = val.value_kind; - tsc.value_index = val.value_index; - tsc.kindFlags = flags; - break; - /*case ParsedSymbol.TYPE_KEYWORD_CLASS: - break; - case ParsedSymbol.TYPE_KEYWORD_FUNCTION: - break; - case ParsedSymbol.TYPE_KEYWORD_METHOD: - case ParsedSymbol.TYPE_KEYWORD_GETTER: - case ParsedSymbol.TYPE_KEYWORD_SETTER: - break;*/ - default: - throw new AVM2ParseException("Unexpected trait type", lexer.yyline()); - } - tsc.name_index = name_index; - return true; - } - - private static int parseNamespaceSet(AVM2ConstantPool constants, Flasm3Lexer lexer) throws AVM2ParseException, IOException { - List namespaceList = new ArrayList<>(); - ParsedSymbol s = lexer.lex(); - if (s.type == ParsedSymbol.TYPE_KEYWORD_NULL) { - return 0; - } - expected(s, ParsedSymbol.TYPE_BRACKET_OPEN, "["); - s = lexer.lex(); - if (s.type != ParsedSymbol.TYPE_BRACKET_CLOSE) { - lexer.pushback(s); - do { - namespaceList.add(parseNamespace(constants, lexer)); - s = lexer.lex(); - } while (s.type == ParsedSymbol.TYPE_COMMA); - expected(s, ParsedSymbol.TYPE_BRACKET_CLOSE, "]"); - } - loopn: - for (int n = 1; n < constants.getNamespaceSetCount(); n++) { - int[] nss = constants.getNamespaceSet(n).namespaces; - if (nss.length != namespaceList.size()) { - continue; - } - for (int i = 0; i < nss.length; i++) { - if (nss[i] != namespaceList.get(i)) { - continue loopn; - } - } - return n; - } - int[] nss = new int[namespaceList.size()]; - for (int i = 0; i < nss.length; i++) { - nss[i] = namespaceList.get(i); - } - return constants.addNamespaceSet(new NamespaceSet(nss)); - } - - private static int parseNamespace(AVM2ConstantPool constants, Flasm3Lexer lexer) throws AVM2ParseException, IOException { - - ParsedSymbol type = lexer.lex(); - int kind = 0; - switch (type.type) { - case ParsedSymbol.TYPE_KEYWORD_NULL: - return 0; - case ParsedSymbol.TYPE_KEYWORD_NAMESPACE: - kind = Namespace.KIND_NAMESPACE; - break; - case ParsedSymbol.TYPE_KEYWORD_PRIVATENAMESPACE: - kind = Namespace.KIND_PRIVATE; - break; - case ParsedSymbol.TYPE_KEYWORD_PACKAGENAMESPACE: - kind = Namespace.KIND_PACKAGE; - break; - case ParsedSymbol.TYPE_KEYWORD_PACKAGEINTERNALNS: - kind = Namespace.KIND_PACKAGE_INTERNAL; - break; - case ParsedSymbol.TYPE_KEYWORD_PROTECTEDNAMESPACE: - kind = Namespace.KIND_PROTECTED; - break; - case ParsedSymbol.TYPE_KEYWORD_EXPLICITNAMESPACE: - kind = Namespace.KIND_EXPLICIT; - break; - case ParsedSymbol.TYPE_KEYWORD_STATICPROTECTEDNS: - kind = Namespace.KIND_STATIC_PROTECTED; - break; - default: - throw new AVM2ParseException("Namespace kind expected", lexer.yyline()); - } - - expected(ParsedSymbol.TYPE_PARENT_OPEN, "(", lexer); - ParsedSymbol name = lexer.lex(); - if (name.type == ParsedSymbol.TYPE_KEYWORD_NULL) { - - } else if (name.type == ParsedSymbol.TYPE_STRING) { - - } else { - throw new AVM2ParseException("String or null expected", lexer.yyline()); - } - ParsedSymbol c = lexer.lex(); - int index = 0; - if (c.type == ParsedSymbol.TYPE_COMMA) { - ParsedSymbol extra = lexer.lex(); - expected(extra, ParsedSymbol.TYPE_STRING, "String"); - try { - index = Integer.parseInt((String) extra.value); - } catch (NumberFormatException nfe) { - throw new AVM2ParseException("Number expected", lexer.yyline()); - } - } else { - lexer.pushback(c); - } - expected(ParsedSymbol.TYPE_PARENT_CLOSE, ")", lexer); - - return constants.getNamespaceId(new Namespace(kind, name.type == ParsedSymbol.TYPE_KEYWORD_NULL ? 0 : constants.getStringId((String) name.value, true)), index, true); - } - - private static int parseMultiName(AVM2ConstantPool constants, Flasm3Lexer lexer) throws AVM2ParseException, IOException { - ParsedSymbol s = lexer.lex(); - int kind = 0; - int name_index = 0; - int namespace_index = 0; - int namespace_set_index = 0; - int qname_index = 0; - List params = new ArrayList<>(); - - switch (s.type) { - case ParsedSymbol.TYPE_KEYWORD_NULL: - return 0; - case ParsedSymbol.TYPE_KEYWORD_QNAME: - kind = Multiname.QNAME; - break; - case ParsedSymbol.TYPE_KEYWORD_QNAMEA: - kind = Multiname.QNAMEA; - break; - case ParsedSymbol.TYPE_KEYWORD_RTQNAME: - kind = Multiname.RTQNAME; - break; - case ParsedSymbol.TYPE_KEYWORD_RTQNAMEA: - kind = Multiname.RTQNAMEA; - break; - case ParsedSymbol.TYPE_KEYWORD_RTQNAMEL: - kind = Multiname.RTQNAMEL; - break; - case ParsedSymbol.TYPE_KEYWORD_RTQNAMELA: - kind = Multiname.RTQNAMELA; - break; - case ParsedSymbol.TYPE_KEYWORD_MULTINAME: - kind = Multiname.MULTINAME; - break; - case ParsedSymbol.TYPE_KEYWORD_MULTINAMEA: - kind = Multiname.MULTINAMEA; - break; - case ParsedSymbol.TYPE_KEYWORD_MULTINAMEL: - kind = Multiname.MULTINAMEL; - break; - case ParsedSymbol.TYPE_KEYWORD_MULTINAMELA: - kind = Multiname.MULTINAMELA; - break; - case ParsedSymbol.TYPE_KEYWORD_TYPENAME: - kind = Multiname.TYPENAME; - break; - default: - throw new AVM2ParseException("Name expected", lexer.yyline()); - } - - switch (s.type) { - case ParsedSymbol.TYPE_KEYWORD_QNAME: - case ParsedSymbol.TYPE_KEYWORD_QNAMEA: - expected(ParsedSymbol.TYPE_PARENT_OPEN, "(", lexer); - namespace_index = parseNamespace(constants, lexer); - expected(ParsedSymbol.TYPE_COMMA, ",", lexer); - ParsedSymbol name = lexer.lex(); - if (name.type == ParsedSymbol.TYPE_KEYWORD_NULL) { - name_index = 0; - } else { - expected(name, ParsedSymbol.TYPE_STRING, "String"); - name_index = constants.getStringId((String) name.value, true); - } - expected(ParsedSymbol.TYPE_PARENT_CLOSE, ")", lexer); - break; - case ParsedSymbol.TYPE_KEYWORD_RTQNAME: - case ParsedSymbol.TYPE_KEYWORD_RTQNAMEA: - expected(ParsedSymbol.TYPE_PARENT_OPEN, "(", lexer); - ParsedSymbol rtqName = lexer.lex(); - if (rtqName.type == ParsedSymbol.TYPE_KEYWORD_NULL) { - name_index = 0; - } else { - expected(rtqName, ParsedSymbol.TYPE_STRING, "String"); - name_index = constants.getStringId((String) rtqName.value, true); - } - expected(ParsedSymbol.TYPE_PARENT_CLOSE, ")", lexer); - break; - case ParsedSymbol.TYPE_KEYWORD_RTQNAMEL: - case ParsedSymbol.TYPE_KEYWORD_RTQNAMELA: - expected(ParsedSymbol.TYPE_PARENT_OPEN, "(", lexer); - expected(ParsedSymbol.TYPE_PARENT_CLOSE, ")", lexer); - break; - case ParsedSymbol.TYPE_KEYWORD_MULTINAME: - case ParsedSymbol.TYPE_KEYWORD_MULTINAMEA: - expected(ParsedSymbol.TYPE_PARENT_OPEN, "(", lexer); - ParsedSymbol mName = lexer.lex(); - if (mName.type == ParsedSymbol.TYPE_KEYWORD_NULL) { - name_index = 0; - } else { - expected(mName, ParsedSymbol.TYPE_STRING, "String"); - name_index = constants.getStringId((String) mName.value, true); - } - expected(ParsedSymbol.TYPE_COMMA, ",", lexer); - namespace_set_index = parseNamespaceSet(constants, lexer); - expected(ParsedSymbol.TYPE_PARENT_CLOSE, ")", lexer); - break; - case ParsedSymbol.TYPE_KEYWORD_MULTINAMEL: - case ParsedSymbol.TYPE_KEYWORD_MULTINAMELA: - expected(ParsedSymbol.TYPE_PARENT_OPEN, "(", lexer); - namespace_set_index = parseNamespaceSet(constants, lexer); - expected(ParsedSymbol.TYPE_PARENT_CLOSE, ")", lexer); - break; - case ParsedSymbol.TYPE_KEYWORD_TYPENAME: - expected(ParsedSymbol.TYPE_PARENT_OPEN, "(", lexer); - qname_index = parseMultiName(constants, lexer); - expected(ParsedSymbol.TYPE_LOWERTHAN, "<", lexer); - params.add(parseMultiName(constants, lexer)); - ParsedSymbol nt = lexer.lex(); - while (nt.type == ParsedSymbol.TYPE_COMMA) { - params.add(parseMultiName(constants, lexer)); - nt = lexer.lex(); - } - expected(nt, ParsedSymbol.TYPE_GREATERTHAN, ">"); - expected(ParsedSymbol.TYPE_PARENT_CLOSE, ")", lexer); - break; - } - - return constants.getMultinameId(new Multiname(kind, name_index, namespace_index, namespace_set_index, qname_index, params), true); - } - - public static ValueKind parseValue(AVM2ConstantPool constants, Flasm3Lexer lexer) throws IOException, AVM2ParseException { - ParsedSymbol type = lexer.lex(); - ParsedSymbol value; - int value_index = 0; - int value_kind = 0; - switch (type.type) { - case ParsedSymbol.TYPE_KEYWORD_INTEGER: - value_kind = ValueKind.CONSTANT_Int; - expected(ParsedSymbol.TYPE_PARENT_OPEN, "(", lexer); - value = lexer.lex(); - if (value.type == ParsedSymbol.TYPE_KEYWORD_NULL) { - value_index = 0; - } else { - expected(value, ParsedSymbol.TYPE_INTEGER, "Integer or null"); - value_index = constants.getIntId((Long) value.value, true); - } - expected(ParsedSymbol.TYPE_PARENT_CLOSE, ")", lexer); - break; - case ParsedSymbol.TYPE_KEYWORD_UINTEGER: - value_kind = ValueKind.CONSTANT_UInt; - expected(ParsedSymbol.TYPE_PARENT_OPEN, "(", lexer); - value = lexer.lex(); - if (value.type == ParsedSymbol.TYPE_KEYWORD_NULL) { - value_index = 0; - } else { - expected(value, ParsedSymbol.TYPE_INTEGER, "UInteger"); - value_index = constants.getUIntId((Long) value.value, true); - } - - expected(ParsedSymbol.TYPE_PARENT_CLOSE, ")", lexer); - break; - case ParsedSymbol.TYPE_KEYWORD_DOUBLE: - value_kind = ValueKind.CONSTANT_Double; - expected(ParsedSymbol.TYPE_PARENT_OPEN, "(", lexer); - value = lexer.lex(); - if (value.type == ParsedSymbol.TYPE_KEYWORD_NULL) { - value_index = 0; - } else { - expected(value, ParsedSymbol.TYPE_FLOAT, "Double or null"); - value_index = constants.getDoubleId((Double) value.value, true); - } - expected(ParsedSymbol.TYPE_PARENT_CLOSE, ")", lexer); - break; - /*case ParsedSymbol.TYPE_KEYWORD_DECIMAL: - value_kind = ValueKind.CONSTANT_Decimal; - break;*/ - case ParsedSymbol.TYPE_INTEGER: - value_kind = ValueKind.CONSTANT_Int; - value_index = constants.getIntId((Long) type.value, true); - break; - case ParsedSymbol.TYPE_FLOAT: - value_kind = ValueKind.CONSTANT_Double; - value_index = constants.getDoubleId((Double) type.value, true); - break; - case ParsedSymbol.TYPE_STRING: - value_kind = ValueKind.CONSTANT_Utf8; - value_index = constants.getStringId((String) type.value, true); - break; - case ParsedSymbol.TYPE_KEYWORD_UTF8: - value_kind = ValueKind.CONSTANT_Utf8; - expected(ParsedSymbol.TYPE_PARENT_OPEN, "(", lexer); - value = lexer.lex(); - if (value.type == ParsedSymbol.TYPE_KEYWORD_NULL) { - value_index = 0; - } else { - expected(value, ParsedSymbol.TYPE_STRING, "String or null"); - expected(ParsedSymbol.TYPE_PARENT_CLOSE, ")", lexer); - value_index = constants.getStringId((String) value.value, true); - } - break; - case ParsedSymbol.TYPE_KEYWORD_TRUE: - value_kind = ValueKind.CONSTANT_True; - break; - case ParsedSymbol.TYPE_KEYWORD_FALSE: - value_kind = ValueKind.CONSTANT_False; - break; - case ParsedSymbol.TYPE_KEYWORD_NULL: - value_kind = ValueKind.CONSTANT_Null; - break; - case ParsedSymbol.TYPE_KEYWORD_NAMESPACE: - case ParsedSymbol.TYPE_KEYWORD_PACKAGEINTERNALNS: - case ParsedSymbol.TYPE_KEYWORD_PROTECTEDNAMESPACE: - case ParsedSymbol.TYPE_KEYWORD_EXPLICITNAMESPACE: - case ParsedSymbol.TYPE_KEYWORD_STATICPROTECTEDNS: - case ParsedSymbol.TYPE_KEYWORD_PRIVATENAMESPACE: - case ParsedSymbol.TYPE_KEYWORD_PACKAGENAMESPACE: - - switch (type.type) { - case ParsedSymbol.TYPE_KEYWORD_NAMESPACE: - value_kind = ValueKind.CONSTANT_Namespace; - break; - case ParsedSymbol.TYPE_KEYWORD_PACKAGEINTERNALNS: - value_kind = ValueKind.CONSTANT_PackageInternalNs; - break; - case ParsedSymbol.TYPE_KEYWORD_PROTECTEDNAMESPACE: - value_kind = ValueKind.CONSTANT_ProtectedNamespace; - break; - case ParsedSymbol.TYPE_KEYWORD_EXPLICITNAMESPACE: - value_kind = ValueKind.CONSTANT_ExplicitNamespace; - break; - case ParsedSymbol.TYPE_KEYWORD_STATICPROTECTEDNS: - value_kind = ValueKind.CONSTANT_StaticProtectedNs; - break; - case ParsedSymbol.TYPE_KEYWORD_PRIVATENAMESPACE: - value_kind = ValueKind.CONSTANT_PrivateNs; - break; - case ParsedSymbol.TYPE_KEYWORD_PACKAGENAMESPACE: - value_kind = ValueKind.CONSTANT_PackageNamespace; - break; - } - lexer.pushback(type); - value_index = parseNamespace(constants, lexer); - break; - default: - if (Configuration.debugMode.get()) { - throw new AVM2ParseException("Not supported valueType.", lexer.yyline()); - } - } - return new ValueKind(value_index, value_kind); - } - - public static AVM2Code parse(Reader reader, AVM2ConstantPool constants, Trait trait, MissingSymbolHandler missingHandler, MethodBody body, MethodInfo info) throws IOException, AVM2ParseException, InterruptedException { - AVM2Code code = new AVM2Code(); - - List offsetItems = new ArrayList<>(); - List labelItems = new ArrayList<>(); - List exceptions = new ArrayList<>(); - List exceptionIndices = new ArrayList<>(); - int offset = 0; - - Flasm3Lexer lexer = new Flasm3Lexer(reader); - - ParsedSymbol symb; - AVM2Instruction lastIns = null; - List exceptionsFrom = new ArrayList<>(); - List exceptionsTo = new ArrayList<>(); - List exceptionsTargets = new ArrayList<>(); - info.flags = 0; - info.name_index = 0; - List paramTypes = new ArrayList<>(); - List paramNames = new ArrayList<>(); - List optional = new ArrayList<>(); - do { - symb = lexer.lex(); - if (Arrays.asList(ParsedSymbol.TYPE_KEYWORD_BODY, ParsedSymbol.TYPE_KEYWORD_CODE, ParsedSymbol.TYPE_KEYWORD_METHOD).contains(symb.type)) { - continue; - } - if (symb.type == ParsedSymbol.TYPE_KEYWORD_TRAIT) { - if (trait == null) { - throw new AVM2ParseException("No trait expected", lexer.yyline()); - } - symb = lexer.lex(); - switch (symb.type) { - case ParsedSymbol.TYPE_KEYWORD_METHOD: - case ParsedSymbol.TYPE_KEYWORD_GETTER: - case ParsedSymbol.TYPE_KEYWORD_SETTER: - if (!(trait instanceof TraitMethodGetterSetter)) { - throw new AVM2ParseException("Unxpected trait type", lexer.yyline()); - } - TraitMethodGetterSetter tm = (TraitMethodGetterSetter) trait; - switch (symb.type) { - case ParsedSymbol.TYPE_KEYWORD_METHOD: - tm.kindType = Trait.TRAIT_METHOD; - break; - case ParsedSymbol.TYPE_KEYWORD_GETTER: - tm.kindType = Trait.TRAIT_GETTER; - break; - case ParsedSymbol.TYPE_KEYWORD_SETTER: - tm.kindType = Trait.TRAIT_SETTER; - break; - } - tm.name_index = parseMultiName(constants, lexer); - expected(ParsedSymbol.TYPE_KEYWORD_DISPID, "dispid", lexer); - symb = lexer.lex(); - expected(symb, ParsedSymbol.TYPE_INTEGER, "Integer"); - tm.disp_id = (int) (long) (Long) symb.value; - - break; - case ParsedSymbol.TYPE_KEYWORD_FUNCTION: - if (!(trait instanceof TraitFunction)) { - throw new AVM2ParseException("Unxpected trait type", lexer.yyline()); - } - break; - - } - continue; - } - if (symb.type == ParsedSymbol.TYPE_KEYWORD_NAME) { - symb = lexer.lex(); - if (symb.type == ParsedSymbol.TYPE_KEYWORD_NULL) { - info.name_index = 0; - } else { - expected(symb, ParsedSymbol.TYPE_STRING, "String or null"); - info.name_index = constants.getStringId((String) symb.value, true); - } - continue; - } - if (symb.type == ParsedSymbol.TYPE_KEYWORD_PARAM) { - paramTypes.add(parseMultiName(constants, lexer)); - continue; - } - if (symb.type == ParsedSymbol.TYPE_KEYWORD_PARAMNAME) { - symb = lexer.lex(); - if (symb.type == ParsedSymbol.TYPE_KEYWORD_NULL) { - paramNames.add(0); - } else { - expected(symb, ParsedSymbol.TYPE_STRING, "String or null"); - paramNames.add(constants.getStringId((String) symb.value, true)); - } - continue; - } - - if (symb.type == ParsedSymbol.TYPE_KEYWORD_OPTIONAL) { - optional.add(parseValue(constants, lexer)); - continue; - } - - if (symb.type == ParsedSymbol.TYPE_KEYWORD_MAXSTACK) { - symb = lexer.lex(); - expected(symb, ParsedSymbol.TYPE_INTEGER, "Integer"); - body.max_stack = (int) (long) (Long) symb.value; - continue; - } - - if (symb.type == ParsedSymbol.TYPE_KEYWORD_LOCALCOUNT) { - symb = lexer.lex(); - expected(symb, ParsedSymbol.TYPE_INTEGER, "Integer"); - body.max_regs = (int) (long) (Long) symb.value; - continue; - } - - if (symb.type == ParsedSymbol.TYPE_KEYWORD_INITSCOPEDEPTH) { - symb = lexer.lex(); - expected(symb, ParsedSymbol.TYPE_INTEGER, "Integer"); - body.init_scope_depth = (int) (long) (Long) symb.value; - continue; - } - - if (symb.type == ParsedSymbol.TYPE_KEYWORD_MAXSCOPEDEPTH) { - symb = lexer.lex(); - expected(symb, ParsedSymbol.TYPE_INTEGER, "Integer"); - body.max_scope_depth = (int) (long) (Long) symb.value; - continue; - } - - if (symb.type == ParsedSymbol.TYPE_KEYWORD_RETURNS) { - info.ret_type = parseMultiName(constants, lexer); - continue; - } - - if (symb.type == ParsedSymbol.TYPE_KEYWORD_FLAG) { - symb = lexer.lex(); - switch (symb.type) { - case ParsedSymbol.TYPE_KEYWORD_EXPLICIT: - info.setFlagExplicit(); - break; - case ParsedSymbol.TYPE_KEYWORD_HAS_OPTIONAL: - info.setFlagHas_optional(); - break; - case ParsedSymbol.TYPE_KEYWORD_HAS_PARAM_NAMES: - info.setFlagHas_paramnames(); - break; - case ParsedSymbol.TYPE_KEYWORD_IGNORE_REST: - info.setFlagIgnore_Rest(); - break; - case ParsedSymbol.TYPE_KEYWORD_NEED_ACTIVATION: - info.setFlagNeed_activation(); - break; - case ParsedSymbol.TYPE_KEYWORD_NEED_ARGUMENTS: - info.setFlagNeed_Arguments(); - break; - case ParsedSymbol.TYPE_KEYWORD_NEED_REST: - info.setFlagNeed_rest(); - break; - case ParsedSymbol.TYPE_KEYWORD_SET_DXNS: - info.setFlagSetsdxns(); - break; - } - continue; - } - if (symb.type == ParsedSymbol.TYPE_KEYWORD_TRY) { - expected(ParsedSymbol.TYPE_KEYWORD_FROM, "From", lexer); - symb = lexer.lex(); - expected(symb, ParsedSymbol.TYPE_IDENTIFIER, "Identifier"); - exceptionsFrom.add((String) symb.value); - expected(ParsedSymbol.TYPE_KEYWORD_TO, "To", lexer); - symb = lexer.lex(); - expected(symb, ParsedSymbol.TYPE_IDENTIFIER, "Identifier"); - exceptionsTo.add((String) symb.value); - expected(ParsedSymbol.TYPE_KEYWORD_TARGET, "Target", lexer); - symb = lexer.lex(); - expected(symb, ParsedSymbol.TYPE_IDENTIFIER, "Identifier"); - exceptionsTargets.add((String) symb.value); - expected(ParsedSymbol.TYPE_KEYWORD_TYPE, "Type", lexer); - ABCException ex = new ABCException(); - ex.type_index = parseMultiName(constants, lexer); - expected(ParsedSymbol.TYPE_KEYWORD_NAME, "Name", lexer); - ex.name_index = parseMultiName(constants, lexer); - exceptions.add(ex); - continue; - } - if (symb.type == ParsedSymbol.TYPE_EXCEPTION_START) { - int exIndex = (Integer) symb.value; - int listIndex = exceptionIndices.indexOf(exIndex); - if (listIndex == -1) { - throw new AVM2ParseException("Undefinex exception index", lexer.yyline()); - } - exceptions.get(listIndex).start = offset; - continue; - } - if (symb.type == ParsedSymbol.TYPE_EXCEPTION_END) { - int exIndex = (Integer) symb.value; - int listIndex = exceptionIndices.indexOf(exIndex); - if (listIndex == -1) { - throw new AVM2ParseException("Undefinex exception index", lexer.yyline()); - } - exceptions.get(listIndex).end = offset; - continue; - } - if (symb.type == ParsedSymbol.TYPE_EXCEPTION_TARGET) { - int exIndex = (Integer) symb.value; - int listIndex = exceptionIndices.indexOf(exIndex); - if (listIndex == -1) { - throw new AVM2ParseException("Undefinex exception index", lexer.yyline()); - } - exceptions.get(listIndex).target = offset; - continue; - } - if (symb.type == ParsedSymbol.TYPE_EOF) { - break; - } - if (symb.type == ParsedSymbol.TYPE_COMMENT) { - if (lastIns != null) { - lastIns.comment = (String) symb.value; - } - continue; - } - if (symb.type == ParsedSymbol.TYPE_INSTRUCTION_NAME) { - if (((String) symb.value).toLowerCase(Locale.ENGLISH).equals("exception")) { - ParsedSymbol exIndex = lexer.lex(); - if (exIndex.type != ParsedSymbol.TYPE_INTEGER) { - throw new AVM2ParseException("Index expected", lexer.yyline()); - } - ParsedSymbol exName = lexer.lex(); - if (exName.type != ParsedSymbol.TYPE_MULTINAME) { - throw new AVM2ParseException("Multiname expected", lexer.yyline()); - } - ParsedSymbol exType = lexer.lex(); - if (exType.type != ParsedSymbol.TYPE_MULTINAME) { - throw new AVM2ParseException("Multiname expected", lexer.yyline()); - } - ABCException ex = new ABCException(); - - ex.name_index = checkMultinameIndex(constants, (int) (long) (Long) exName.value, lexer.yyline()); - ex.type_index = checkMultinameIndex(constants, (int) (long) (Long) exType.value, lexer.yyline()); - exceptions.add(ex); - exceptionIndices.add((int) (long) (Long) exIndex.value); - continue; - } - boolean insFound = false; - for (InstructionDefinition def : AVM2Code.instructionSet) { - if (def != null && def.instructionName.equals((String) symb.value)) { - insFound = true; - List operandsList = new ArrayList<>(); - - for (int i = 0; i < def.operands.length; i++) { - ParsedSymbol parsedOperand = lexer.lex(); - switch (def.operands[i]) { - case AVM2Code.DAT_MULTINAME_INDEX: - lexer.pushback(parsedOperand); - operandsList.add(parseMultiName(constants, lexer)); - /*if (parsedOperand.type == ParsedSymbol.TYPE_MULTINAME) { - operandsList.add(checkMultinameIndex(constants, (int) (long) (Long) parsedOperand.value, lexer.yyline())); - } else { - throw new ParseException("Multiname expected", lexer.yyline()); - }*/ - break; - case AVM2Code.DAT_STRING_INDEX: - if (parsedOperand.type == ParsedSymbol.TYPE_KEYWORD_NULL) { - operandsList.add(0); - } else { - if (parsedOperand.type == ParsedSymbol.TYPE_STRING) { - int sid = constants.getStringId((String) parsedOperand.value); - if (sid == 0) { - if ((missingHandler != null) && (missingHandler.missingString((String) parsedOperand.value))) { - sid = constants.addString((String) parsedOperand.value); - } else { - throw new AVM2ParseException("Unknown String", lexer.yyline()); - } - } - operandsList.add(sid); - } else { - throw new AVM2ParseException("String or null expected", lexer.yyline()); - } - } - break; - case AVM2Code.DAT_INT_INDEX: - - if (parsedOperand.type == ParsedSymbol.TYPE_KEYWORD_NULL) { - operandsList.add(0); - } else { - if (parsedOperand.type == ParsedSymbol.TYPE_INTEGER) { - long intVal = (Long) parsedOperand.value; - int iid = constants.getIntId(intVal); - if (iid == 0) { - if ((missingHandler != null) && (missingHandler.missingInt(intVal))) { - iid = constants.addInt(intVal); - } else { - throw new AVM2ParseException("Unknown int", lexer.yyline()); - } - } - operandsList.add(iid); - } else { - throw new AVM2ParseException("Integer or null expected", lexer.yyline()); - } - } - break; - case AVM2Code.DAT_UINT_INDEX: - if (parsedOperand.type == ParsedSymbol.TYPE_KEYWORD_NULL) { - operandsList.add(0); - } else { - if (parsedOperand.type == ParsedSymbol.TYPE_INTEGER) { - long intVal = (Long) parsedOperand.value; - int iid = constants.getUIntId(intVal); - if (iid == 0) { - if ((missingHandler != null) && (missingHandler.missingUInt(intVal))) { - iid = constants.addUInt(intVal); - } else { - throw new AVM2ParseException("Unknown uint", lexer.yyline()); - } - } - operandsList.add(iid); - } else { - throw new AVM2ParseException("Integer or null expected", lexer.yyline()); - } - } - break; - case AVM2Code.DAT_DOUBLE_INDEX: - if (parsedOperand.type == ParsedSymbol.TYPE_KEYWORD_NULL) { - operandsList.add(0); - } else { - if ((parsedOperand.type == ParsedSymbol.TYPE_INTEGER) || (parsedOperand.type == ParsedSymbol.TYPE_FLOAT)) { - - double doubleVal = 0; - if (parsedOperand.type == ParsedSymbol.TYPE_INTEGER) { - doubleVal = (Long) parsedOperand.value; - } - if (parsedOperand.type == ParsedSymbol.TYPE_FLOAT) { - doubleVal = (Double) parsedOperand.value; - } - int did = constants.getDoubleId(doubleVal); - if (did == 0) { - if ((missingHandler != null) && (missingHandler.missingDouble(doubleVal))) { - did = constants.addDouble(doubleVal); - } else { - throw new AVM2ParseException("Unknown double", lexer.yyline()); - } - } - operandsList.add(did); - } else { - throw new AVM2ParseException("Float or null expected", lexer.yyline()); - } - } - break; - case AVM2Code.DAT_OFFSET: - if (parsedOperand.type == ParsedSymbol.TYPE_IDENTIFIER) { - offsetItems.add(new OffsetItem((String) parsedOperand.value, code.code.size(), i)); - operandsList.add(0); - } else { - throw new AVM2ParseException("Offset expected", lexer.yyline()); - } - break; - case AVM2Code.DAT_CASE_BASEOFFSET: - if (parsedOperand.type == ParsedSymbol.TYPE_IDENTIFIER) { - offsetItems.add(new CaseOffsetItem((String) parsedOperand.value, code.code.size(), i)); - operandsList.add(0); - } else { - throw new AVM2ParseException("Offset expected", lexer.yyline()); - } - break; - case AVM2Code.OPT_CASE_OFFSETS: - - if (parsedOperand.type == ParsedSymbol.TYPE_INTEGER) { - int patCount = (int) (long) (Long) parsedOperand.value; - operandsList.add(patCount); - - for (int c = 0; c <= patCount; c++) { - parsedOperand = lexer.lex(); - if (parsedOperand.type == ParsedSymbol.TYPE_IDENTIFIER) { - offsetItems.add(new CaseOffsetItem((String) parsedOperand.value, code.code.size(), i + (c + 1))); - operandsList.add(0); - } else { - throw new AVM2ParseException("Offset expected", lexer.yyline()); - } - } - } else { - throw new AVM2ParseException("Case count expected", lexer.yyline()); - } - break; - case AVM2Code.OPT_BYTE: - if (parsedOperand.type == ParsedSymbol.TYPE_INTEGER) { - long val = (long) (Long) parsedOperand.value; - if (val < Byte.MIN_VALUE || val > Byte.MAX_VALUE) { - throw new AVM2ParseException("Byte value expected (" + Byte.MIN_VALUE + " to " + Byte.MAX_VALUE + "). Use pushshort or pushint to push larger values", lexer.yyline()); - } - operandsList.add((int) val); - } else { - throw new AVM2ParseException("Integer expected", lexer.yyline()); - } - break; - default: - if (parsedOperand.type == ParsedSymbol.TYPE_INTEGER) { - long val = (long) (Long) parsedOperand.value; - if (def instanceof PushShortIns) { - if (val < Short.MIN_VALUE || val > Short.MAX_VALUE) { - throw new AVM2ParseException("Short value expected (" + Short.MIN_VALUE + " to " + Short.MAX_VALUE + "). Use pushint to push larger values", lexer.yyline()); - } - } - operandsList.add((int) val); - } else { - throw new AVM2ParseException("Integer expected", lexer.yyline()); - } - } - } - - int[] operands = new int[operandsList.size()]; - for (int i = 0; i < operandsList.size(); i++) { - operands[i] = operandsList.get(i); - } - lastIns = new AVM2Instruction(offset, def, operands); - code.code.add(lastIns); - offset += lastIns.getBytesLength(); - break; - } - } - if (symb.value.toString().toLowerCase().equals("ffdec_deobfuscatepop")) { - lastIns = new AVM2Instruction(offset, new DeobfuscatePopIns(), null); - code.code.add(lastIns); - offset += lastIns.getBytesLength(); - insFound = true; - } - if (!insFound) { - throw new AVM2ParseException("Invalid instruction name:" + (String) symb.value, lexer.yyline()); - } - } else if (symb.type == ParsedSymbol.TYPE_LABEL) { - labelItems.add(new LabelItem((String) symb.value, offset)); - - } else { - throw new AVM2ParseException("Unexpected symbol", lexer.yyline()); - } - } while (symb.type != ParsedSymbol.TYPE_EOF); - - code.compact(); - for (LabelItem li : labelItems) { - int ind; - ind = exceptionsFrom.indexOf(li.label); - if (ind > -1) { - exceptions.get(ind).start = li.offset; - } - - ind = exceptionsTo.indexOf(li.label); - if (ind > -1) { - exceptions.get(ind).end = li.offset; - } - - ind = exceptionsTargets.indexOf(li.label); - if (ind > -1) { - exceptions.get(ind).target = li.offset; - } - } - - for (OffsetItem oi : offsetItems) { - for (LabelItem li : labelItems) { - if (Thread.currentThread().isInterrupted()) { - throw new InterruptedException(); - } - if (oi.label.equals(li.label)) { - AVM2Instruction ins = code.code.get((int) oi.insPosition); - int relOffset; - if (oi instanceof CaseOffsetItem) { - relOffset = li.offset - (int) ins.offset; - } else { - relOffset = li.offset - ((int) ins.offset + ins.getBytesLength()); - } - ins.operands[oi.insOperandIndex] = relOffset; - } - } - } - body.exceptions = new ABCException[exceptions.size()]; - for (int e = 0; e < exceptions.size(); e++) { - body.exceptions[e] = exceptions.get(e); - } - - info.param_types = new int[paramTypes.size()]; - for (int i = 0; i < paramTypes.size(); i++) { - info.param_types[i] = paramTypes.get(i); - } - - if (info.flagHas_paramnames()) { - info.paramNames = new int[paramNames.size()]; - for (int i = 0; i < paramNames.size(); i++) { - info.paramNames[i] = paramNames.get(i); - } - } - - if (info.flagHas_optional()) { - info.optional = new ValueKind[optional.size()]; - for (int i = 0; i < optional.size(); i++) { - info.optional[i] = optional.get(i); - } - } - return code; - } -} +/* + * Copyright (C) 2010-2015 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.flash.abc.avm2.parser.pcode; + +import com.jpexs.decompiler.flash.abc.avm2.AVM2Code; +import com.jpexs.decompiler.flash.abc.avm2.AVM2ConstantPool; +import com.jpexs.decompiler.flash.abc.avm2.instructions.AVM2Instruction; +import com.jpexs.decompiler.flash.abc.avm2.instructions.DeobfuscatePopIns; +import com.jpexs.decompiler.flash.abc.avm2.instructions.InstructionDefinition; +import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.PushShortIns; +import com.jpexs.decompiler.flash.abc.avm2.parser.AVM2ParseException; +import com.jpexs.decompiler.flash.abc.types.ABCException; +import com.jpexs.decompiler.flash.abc.types.MethodBody; +import com.jpexs.decompiler.flash.abc.types.MethodInfo; +import com.jpexs.decompiler.flash.abc.types.Multiname; +import com.jpexs.decompiler.flash.abc.types.Namespace; +import com.jpexs.decompiler.flash.abc.types.NamespaceSet; +import com.jpexs.decompiler.flash.abc.types.ValueKind; +import com.jpexs.decompiler.flash.abc.types.traits.Trait; +import com.jpexs.decompiler.flash.abc.types.traits.TraitFunction; +import com.jpexs.decompiler.flash.abc.types.traits.TraitMethodGetterSetter; +import com.jpexs.decompiler.flash.abc.types.traits.TraitSlotConst; +import com.jpexs.decompiler.flash.configuration.Configuration; +import java.io.IOException; +import java.io.Reader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + +public class ASM3Parser { + + private static class OffsetItem { + + public String label = ""; + + public long insPosition; + + public int insOperandIndex; + + public OffsetItem(String label, long insOffset, int insOperandIndex) { + this.label = label; + this.insPosition = insOffset; + this.insOperandIndex = insOperandIndex; + } + } + + private static class CaseOffsetItem extends OffsetItem { + + public CaseOffsetItem(String label, long insOffset, int insOperandIndex) { + super(label, insOffset, insOperandIndex); + } + } + + private static class LabelItem { + + public String label = ""; + + public int offset; + + public LabelItem(String label, int offset) { + this.label = label; + this.offset = offset; + } + } + + public static AVM2Code parse(Reader reader, AVM2ConstantPool constants, Trait trait, MethodBody body, MethodInfo info) throws IOException, AVM2ParseException, InterruptedException { + return parse(reader, constants, trait, null, body, info); + } + + private static int checkMultinameIndex(AVM2ConstantPool constants, int index, int line) throws AVM2ParseException { + if ((index < 0) || (index >= constants.getMultinameCount())) { + throw new AVM2ParseException("Invalid multiname index", line); + } + return index; + } + + private static void expected(int type, String expStr, Flasm3Lexer lexer) throws IOException, AVM2ParseException { + ParsedSymbol s = lexer.lex(); + if (s.type != type) { + throw new AVM2ParseException(expStr + " expected", lexer.yyline()); + } + } + + private static void expected(ParsedSymbol s, int type, String expStr) throws IOException, AVM2ParseException { + if (s.type != type) { + throw new AVM2ParseException(expStr + " expected", 0); + } + } + + public static boolean parseSlotConst(Reader reader, AVM2ConstantPool constants, TraitSlotConst tsc) throws IOException, AVM2ParseException { + Flasm3Lexer lexer = new Flasm3Lexer(reader); + expected(ParsedSymbol.TYPE_KEYWORD_TRAIT, "trait", lexer); + int name_index = parseMultiName(constants, lexer); + + ParsedSymbol symb = lexer.lex(); + + int flags = 0; + while (symb.type == ParsedSymbol.TYPE_KEYWORD_FLAG) { + symb = lexer.lex(); + switch (symb.type) { + case ParsedSymbol.TYPE_KEYWORD_FINAL: + flags |= Trait.ATTR_Final; + break; + case ParsedSymbol.TYPE_KEYWORD_OVERRIDE: + flags |= Trait.ATTR_Override; + break; + case ParsedSymbol.TYPE_KEYWORD_METADATA: + flags |= Trait.ATTR_Metadata; + break; + default: + throw new AVM2ParseException("Invalid trait flag", lexer.yyline()); + } + symb = lexer.lex(); + } + + switch (symb.type) { + case ParsedSymbol.TYPE_KEYWORD_SLOT: + case ParsedSymbol.TYPE_KEYWORD_CONST: + expected(ParsedSymbol.TYPE_KEYWORD_SLOTID, "slotid", lexer); + symb = lexer.lex(); + expected(symb, ParsedSymbol.TYPE_INTEGER, "Integer"); + int slotid = (int) (long) (Long) symb.value; + expected(ParsedSymbol.TYPE_KEYWORD_TYPE, "type", lexer); + int type = parseMultiName(constants, lexer); + expected(ParsedSymbol.TYPE_KEYWORD_VALUE, "value", lexer); + ValueKind val = parseValue(constants, lexer); + tsc.slot_id = slotid; + tsc.type_index = type; + tsc.value_kind = val.value_kind; + tsc.value_index = val.value_index; + tsc.kindFlags = flags; + break; + /*case ParsedSymbol.TYPE_KEYWORD_CLASS: + break; + case ParsedSymbol.TYPE_KEYWORD_FUNCTION: + break; + case ParsedSymbol.TYPE_KEYWORD_METHOD: + case ParsedSymbol.TYPE_KEYWORD_GETTER: + case ParsedSymbol.TYPE_KEYWORD_SETTER: + break;*/ + default: + throw new AVM2ParseException("Unexpected trait type", lexer.yyline()); + } + tsc.name_index = name_index; + return true; + } + + private static int parseNamespaceSet(AVM2ConstantPool constants, Flasm3Lexer lexer) throws AVM2ParseException, IOException { + List namespaceList = new ArrayList<>(); + ParsedSymbol s = lexer.lex(); + if (s.type == ParsedSymbol.TYPE_KEYWORD_NULL) { + return 0; + } + expected(s, ParsedSymbol.TYPE_BRACKET_OPEN, "["); + s = lexer.lex(); + if (s.type != ParsedSymbol.TYPE_BRACKET_CLOSE) { + lexer.pushback(s); + do { + namespaceList.add(parseNamespace(constants, lexer)); + s = lexer.lex(); + } while (s.type == ParsedSymbol.TYPE_COMMA); + expected(s, ParsedSymbol.TYPE_BRACKET_CLOSE, "]"); + } + loopn: + for (int n = 1; n < constants.getNamespaceSetCount(); n++) { + int[] nss = constants.getNamespaceSet(n).namespaces; + if (nss.length != namespaceList.size()) { + continue; + } + for (int i = 0; i < nss.length; i++) { + if (nss[i] != namespaceList.get(i)) { + continue loopn; + } + } + return n; + } + int[] nss = new int[namespaceList.size()]; + for (int i = 0; i < nss.length; i++) { + nss[i] = namespaceList.get(i); + } + return constants.addNamespaceSet(new NamespaceSet(nss)); + } + + private static int parseNamespace(AVM2ConstantPool constants, Flasm3Lexer lexer) throws AVM2ParseException, IOException { + + ParsedSymbol type = lexer.lex(); + int kind = 0; + switch (type.type) { + case ParsedSymbol.TYPE_KEYWORD_NULL: + return 0; + case ParsedSymbol.TYPE_KEYWORD_NAMESPACE: + kind = Namespace.KIND_NAMESPACE; + break; + case ParsedSymbol.TYPE_KEYWORD_PRIVATENAMESPACE: + kind = Namespace.KIND_PRIVATE; + break; + case ParsedSymbol.TYPE_KEYWORD_PACKAGENAMESPACE: + kind = Namespace.KIND_PACKAGE; + break; + case ParsedSymbol.TYPE_KEYWORD_PACKAGEINTERNALNS: + kind = Namespace.KIND_PACKAGE_INTERNAL; + break; + case ParsedSymbol.TYPE_KEYWORD_PROTECTEDNAMESPACE: + kind = Namespace.KIND_PROTECTED; + break; + case ParsedSymbol.TYPE_KEYWORD_EXPLICITNAMESPACE: + kind = Namespace.KIND_EXPLICIT; + break; + case ParsedSymbol.TYPE_KEYWORD_STATICPROTECTEDNS: + kind = Namespace.KIND_STATIC_PROTECTED; + break; + default: + throw new AVM2ParseException("Namespace kind expected", lexer.yyline()); + } + + expected(ParsedSymbol.TYPE_PARENT_OPEN, "(", lexer); + ParsedSymbol name = lexer.lex(); + if (name.type == ParsedSymbol.TYPE_KEYWORD_NULL) { + + } else if (name.type == ParsedSymbol.TYPE_STRING) { + + } else { + throw new AVM2ParseException("String or null expected", lexer.yyline()); + } + ParsedSymbol c = lexer.lex(); + int index = 0; + if (c.type == ParsedSymbol.TYPE_COMMA) { + ParsedSymbol extra = lexer.lex(); + expected(extra, ParsedSymbol.TYPE_STRING, "String"); + try { + index = Integer.parseInt((String) extra.value); + } catch (NumberFormatException nfe) { + throw new AVM2ParseException("Number expected", lexer.yyline()); + } + } else { + lexer.pushback(c); + } + expected(ParsedSymbol.TYPE_PARENT_CLOSE, ")", lexer); + + return constants.getNamespaceId(new Namespace(kind, name.type == ParsedSymbol.TYPE_KEYWORD_NULL ? 0 : constants.getStringId((String) name.value, true)), index, true); + } + + private static int parseMultiName(AVM2ConstantPool constants, Flasm3Lexer lexer) throws AVM2ParseException, IOException { + ParsedSymbol s = lexer.lex(); + int kind = 0; + int name_index = 0; + int namespace_index = 0; + int namespace_set_index = 0; + int qname_index = 0; + List params = new ArrayList<>(); + + switch (s.type) { + case ParsedSymbol.TYPE_KEYWORD_NULL: + return 0; + case ParsedSymbol.TYPE_KEYWORD_QNAME: + kind = Multiname.QNAME; + break; + case ParsedSymbol.TYPE_KEYWORD_QNAMEA: + kind = Multiname.QNAMEA; + break; + case ParsedSymbol.TYPE_KEYWORD_RTQNAME: + kind = Multiname.RTQNAME; + break; + case ParsedSymbol.TYPE_KEYWORD_RTQNAMEA: + kind = Multiname.RTQNAMEA; + break; + case ParsedSymbol.TYPE_KEYWORD_RTQNAMEL: + kind = Multiname.RTQNAMEL; + break; + case ParsedSymbol.TYPE_KEYWORD_RTQNAMELA: + kind = Multiname.RTQNAMELA; + break; + case ParsedSymbol.TYPE_KEYWORD_MULTINAME: + kind = Multiname.MULTINAME; + break; + case ParsedSymbol.TYPE_KEYWORD_MULTINAMEA: + kind = Multiname.MULTINAMEA; + break; + case ParsedSymbol.TYPE_KEYWORD_MULTINAMEL: + kind = Multiname.MULTINAMEL; + break; + case ParsedSymbol.TYPE_KEYWORD_MULTINAMELA: + kind = Multiname.MULTINAMELA; + break; + case ParsedSymbol.TYPE_KEYWORD_TYPENAME: + kind = Multiname.TYPENAME; + break; + default: + throw new AVM2ParseException("Name expected", lexer.yyline()); + } + + switch (s.type) { + case ParsedSymbol.TYPE_KEYWORD_QNAME: + case ParsedSymbol.TYPE_KEYWORD_QNAMEA: + expected(ParsedSymbol.TYPE_PARENT_OPEN, "(", lexer); + namespace_index = parseNamespace(constants, lexer); + expected(ParsedSymbol.TYPE_COMMA, ",", lexer); + ParsedSymbol name = lexer.lex(); + if (name.type == ParsedSymbol.TYPE_KEYWORD_NULL) { + name_index = 0; + } else { + expected(name, ParsedSymbol.TYPE_STRING, "String"); + name_index = constants.getStringId((String) name.value, true); + } + expected(ParsedSymbol.TYPE_PARENT_CLOSE, ")", lexer); + break; + case ParsedSymbol.TYPE_KEYWORD_RTQNAME: + case ParsedSymbol.TYPE_KEYWORD_RTQNAMEA: + expected(ParsedSymbol.TYPE_PARENT_OPEN, "(", lexer); + ParsedSymbol rtqName = lexer.lex(); + if (rtqName.type == ParsedSymbol.TYPE_KEYWORD_NULL) { + name_index = 0; + } else { + expected(rtqName, ParsedSymbol.TYPE_STRING, "String"); + name_index = constants.getStringId((String) rtqName.value, true); + } + expected(ParsedSymbol.TYPE_PARENT_CLOSE, ")", lexer); + break; + case ParsedSymbol.TYPE_KEYWORD_RTQNAMEL: + case ParsedSymbol.TYPE_KEYWORD_RTQNAMELA: + expected(ParsedSymbol.TYPE_PARENT_OPEN, "(", lexer); + expected(ParsedSymbol.TYPE_PARENT_CLOSE, ")", lexer); + break; + case ParsedSymbol.TYPE_KEYWORD_MULTINAME: + case ParsedSymbol.TYPE_KEYWORD_MULTINAMEA: + expected(ParsedSymbol.TYPE_PARENT_OPEN, "(", lexer); + ParsedSymbol mName = lexer.lex(); + if (mName.type == ParsedSymbol.TYPE_KEYWORD_NULL) { + name_index = 0; + } else { + expected(mName, ParsedSymbol.TYPE_STRING, "String"); + name_index = constants.getStringId((String) mName.value, true); + } + expected(ParsedSymbol.TYPE_COMMA, ",", lexer); + namespace_set_index = parseNamespaceSet(constants, lexer); + expected(ParsedSymbol.TYPE_PARENT_CLOSE, ")", lexer); + break; + case ParsedSymbol.TYPE_KEYWORD_MULTINAMEL: + case ParsedSymbol.TYPE_KEYWORD_MULTINAMELA: + expected(ParsedSymbol.TYPE_PARENT_OPEN, "(", lexer); + namespace_set_index = parseNamespaceSet(constants, lexer); + expected(ParsedSymbol.TYPE_PARENT_CLOSE, ")", lexer); + break; + case ParsedSymbol.TYPE_KEYWORD_TYPENAME: + expected(ParsedSymbol.TYPE_PARENT_OPEN, "(", lexer); + qname_index = parseMultiName(constants, lexer); + expected(ParsedSymbol.TYPE_LOWERTHAN, "<", lexer); + params.add(parseMultiName(constants, lexer)); + ParsedSymbol nt = lexer.lex(); + while (nt.type == ParsedSymbol.TYPE_COMMA) { + params.add(parseMultiName(constants, lexer)); + nt = lexer.lex(); + } + expected(nt, ParsedSymbol.TYPE_GREATERTHAN, ">"); + expected(ParsedSymbol.TYPE_PARENT_CLOSE, ")", lexer); + break; + } + + return constants.getMultinameId(new Multiname(kind, name_index, namespace_index, namespace_set_index, qname_index, params), true); + } + + public static ValueKind parseValue(AVM2ConstantPool constants, Flasm3Lexer lexer) throws IOException, AVM2ParseException { + ParsedSymbol type = lexer.lex(); + ParsedSymbol value; + int value_index = 0; + int value_kind = 0; + switch (type.type) { + case ParsedSymbol.TYPE_KEYWORD_INTEGER: + value_kind = ValueKind.CONSTANT_Int; + expected(ParsedSymbol.TYPE_PARENT_OPEN, "(", lexer); + value = lexer.lex(); + if (value.type == ParsedSymbol.TYPE_KEYWORD_NULL) { + value_index = 0; + } else { + expected(value, ParsedSymbol.TYPE_INTEGER, "Integer or null"); + value_index = constants.getIntId((Long) value.value, true); + } + expected(ParsedSymbol.TYPE_PARENT_CLOSE, ")", lexer); + break; + case ParsedSymbol.TYPE_KEYWORD_UINTEGER: + value_kind = ValueKind.CONSTANT_UInt; + expected(ParsedSymbol.TYPE_PARENT_OPEN, "(", lexer); + value = lexer.lex(); + if (value.type == ParsedSymbol.TYPE_KEYWORD_NULL) { + value_index = 0; + } else { + expected(value, ParsedSymbol.TYPE_INTEGER, "UInteger"); + value_index = constants.getUIntId((Long) value.value, true); + } + + expected(ParsedSymbol.TYPE_PARENT_CLOSE, ")", lexer); + break; + case ParsedSymbol.TYPE_KEYWORD_DOUBLE: + value_kind = ValueKind.CONSTANT_Double; + expected(ParsedSymbol.TYPE_PARENT_OPEN, "(", lexer); + value = lexer.lex(); + if (value.type == ParsedSymbol.TYPE_KEYWORD_NULL) { + value_index = 0; + } else { + expected(value, ParsedSymbol.TYPE_FLOAT, "Double or null"); + value_index = constants.getDoubleId((Double) value.value, true); + } + expected(ParsedSymbol.TYPE_PARENT_CLOSE, ")", lexer); + break; + /*case ParsedSymbol.TYPE_KEYWORD_DECIMAL: + value_kind = ValueKind.CONSTANT_Decimal; + break;*/ + case ParsedSymbol.TYPE_INTEGER: + value_kind = ValueKind.CONSTANT_Int; + value_index = constants.getIntId((Long) type.value, true); + break; + case ParsedSymbol.TYPE_FLOAT: + value_kind = ValueKind.CONSTANT_Double; + value_index = constants.getDoubleId((Double) type.value, true); + break; + case ParsedSymbol.TYPE_STRING: + value_kind = ValueKind.CONSTANT_Utf8; + value_index = constants.getStringId((String) type.value, true); + break; + case ParsedSymbol.TYPE_KEYWORD_UTF8: + value_kind = ValueKind.CONSTANT_Utf8; + expected(ParsedSymbol.TYPE_PARENT_OPEN, "(", lexer); + value = lexer.lex(); + if (value.type == ParsedSymbol.TYPE_KEYWORD_NULL) { + value_index = 0; + } else { + expected(value, ParsedSymbol.TYPE_STRING, "String or null"); + expected(ParsedSymbol.TYPE_PARENT_CLOSE, ")", lexer); + value_index = constants.getStringId((String) value.value, true); + } + break; + case ParsedSymbol.TYPE_KEYWORD_TRUE: + value_kind = ValueKind.CONSTANT_True; + break; + case ParsedSymbol.TYPE_KEYWORD_FALSE: + value_kind = ValueKind.CONSTANT_False; + break; + case ParsedSymbol.TYPE_KEYWORD_NULL: + value_kind = ValueKind.CONSTANT_Null; + break; + case ParsedSymbol.TYPE_KEYWORD_NAMESPACE: + case ParsedSymbol.TYPE_KEYWORD_PACKAGEINTERNALNS: + case ParsedSymbol.TYPE_KEYWORD_PROTECTEDNAMESPACE: + case ParsedSymbol.TYPE_KEYWORD_EXPLICITNAMESPACE: + case ParsedSymbol.TYPE_KEYWORD_STATICPROTECTEDNS: + case ParsedSymbol.TYPE_KEYWORD_PRIVATENAMESPACE: + case ParsedSymbol.TYPE_KEYWORD_PACKAGENAMESPACE: + + switch (type.type) { + case ParsedSymbol.TYPE_KEYWORD_NAMESPACE: + value_kind = ValueKind.CONSTANT_Namespace; + break; + case ParsedSymbol.TYPE_KEYWORD_PACKAGEINTERNALNS: + value_kind = ValueKind.CONSTANT_PackageInternalNs; + break; + case ParsedSymbol.TYPE_KEYWORD_PROTECTEDNAMESPACE: + value_kind = ValueKind.CONSTANT_ProtectedNamespace; + break; + case ParsedSymbol.TYPE_KEYWORD_EXPLICITNAMESPACE: + value_kind = ValueKind.CONSTANT_ExplicitNamespace; + break; + case ParsedSymbol.TYPE_KEYWORD_STATICPROTECTEDNS: + value_kind = ValueKind.CONSTANT_StaticProtectedNs; + break; + case ParsedSymbol.TYPE_KEYWORD_PRIVATENAMESPACE: + value_kind = ValueKind.CONSTANT_PrivateNs; + break; + case ParsedSymbol.TYPE_KEYWORD_PACKAGENAMESPACE: + value_kind = ValueKind.CONSTANT_PackageNamespace; + break; + } + lexer.pushback(type); + value_index = parseNamespace(constants, lexer); + break; + default: + if (Configuration.debugMode.get()) { + throw new AVM2ParseException("Not supported valueType.", lexer.yyline()); + } + } + return new ValueKind(value_index, value_kind); + } + + public static AVM2Code parse(Reader reader, AVM2ConstantPool constants, Trait trait, MissingSymbolHandler missingHandler, MethodBody body, MethodInfo info) throws IOException, AVM2ParseException, InterruptedException { + AVM2Code code = new AVM2Code(); + + List offsetItems = new ArrayList<>(); + List labelItems = new ArrayList<>(); + List exceptions = new ArrayList<>(); + List exceptionIndices = new ArrayList<>(); + int offset = 0; + + Flasm3Lexer lexer = new Flasm3Lexer(reader); + + ParsedSymbol symb; + AVM2Instruction lastIns = null; + List exceptionsFrom = new ArrayList<>(); + List exceptionsTo = new ArrayList<>(); + List exceptionsTargets = new ArrayList<>(); + info.flags = 0; + info.name_index = 0; + List paramTypes = new ArrayList<>(); + List paramNames = new ArrayList<>(); + List optional = new ArrayList<>(); + do { + symb = lexer.lex(); + if (Arrays.asList(ParsedSymbol.TYPE_KEYWORD_BODY, ParsedSymbol.TYPE_KEYWORD_CODE, ParsedSymbol.TYPE_KEYWORD_METHOD).contains(symb.type)) { + continue; + } + if (symb.type == ParsedSymbol.TYPE_KEYWORD_TRAIT) { + if (trait == null) { + throw new AVM2ParseException("No trait expected", lexer.yyline()); + } + symb = lexer.lex(); + switch (symb.type) { + case ParsedSymbol.TYPE_KEYWORD_METHOD: + case ParsedSymbol.TYPE_KEYWORD_GETTER: + case ParsedSymbol.TYPE_KEYWORD_SETTER: + if (!(trait instanceof TraitMethodGetterSetter)) { + throw new AVM2ParseException("Unxpected trait type", lexer.yyline()); + } + TraitMethodGetterSetter tm = (TraitMethodGetterSetter) trait; + switch (symb.type) { + case ParsedSymbol.TYPE_KEYWORD_METHOD: + tm.kindType = Trait.TRAIT_METHOD; + break; + case ParsedSymbol.TYPE_KEYWORD_GETTER: + tm.kindType = Trait.TRAIT_GETTER; + break; + case ParsedSymbol.TYPE_KEYWORD_SETTER: + tm.kindType = Trait.TRAIT_SETTER; + break; + } + tm.name_index = parseMultiName(constants, lexer); + expected(ParsedSymbol.TYPE_KEYWORD_DISPID, "dispid", lexer); + symb = lexer.lex(); + expected(symb, ParsedSymbol.TYPE_INTEGER, "Integer"); + tm.disp_id = (int) (long) (Long) symb.value; + + break; + case ParsedSymbol.TYPE_KEYWORD_FUNCTION: + if (!(trait instanceof TraitFunction)) { + throw new AVM2ParseException("Unxpected trait type", lexer.yyline()); + } + break; + + } + continue; + } + if (symb.type == ParsedSymbol.TYPE_KEYWORD_NAME) { + symb = lexer.lex(); + if (symb.type == ParsedSymbol.TYPE_KEYWORD_NULL) { + info.name_index = 0; + } else { + expected(symb, ParsedSymbol.TYPE_STRING, "String or null"); + info.name_index = constants.getStringId((String) symb.value, true); + } + continue; + } + if (symb.type == ParsedSymbol.TYPE_KEYWORD_PARAM) { + paramTypes.add(parseMultiName(constants, lexer)); + continue; + } + if (symb.type == ParsedSymbol.TYPE_KEYWORD_PARAMNAME) { + symb = lexer.lex(); + if (symb.type == ParsedSymbol.TYPE_KEYWORD_NULL) { + paramNames.add(0); + } else { + expected(symb, ParsedSymbol.TYPE_STRING, "String or null"); + paramNames.add(constants.getStringId((String) symb.value, true)); + } + continue; + } + + if (symb.type == ParsedSymbol.TYPE_KEYWORD_OPTIONAL) { + optional.add(parseValue(constants, lexer)); + continue; + } + + if (symb.type == ParsedSymbol.TYPE_KEYWORD_MAXSTACK) { + symb = lexer.lex(); + expected(symb, ParsedSymbol.TYPE_INTEGER, "Integer"); + body.max_stack = (int) (long) (Long) symb.value; + continue; + } + + if (symb.type == ParsedSymbol.TYPE_KEYWORD_LOCALCOUNT) { + symb = lexer.lex(); + expected(symb, ParsedSymbol.TYPE_INTEGER, "Integer"); + body.max_regs = (int) (long) (Long) symb.value; + continue; + } + + if (symb.type == ParsedSymbol.TYPE_KEYWORD_INITSCOPEDEPTH) { + symb = lexer.lex(); + expected(symb, ParsedSymbol.TYPE_INTEGER, "Integer"); + body.init_scope_depth = (int) (long) (Long) symb.value; + continue; + } + + if (symb.type == ParsedSymbol.TYPE_KEYWORD_MAXSCOPEDEPTH) { + symb = lexer.lex(); + expected(symb, ParsedSymbol.TYPE_INTEGER, "Integer"); + body.max_scope_depth = (int) (long) (Long) symb.value; + continue; + } + + if (symb.type == ParsedSymbol.TYPE_KEYWORD_RETURNS) { + info.ret_type = parseMultiName(constants, lexer); + continue; + } + + if (symb.type == ParsedSymbol.TYPE_KEYWORD_FLAG) { + symb = lexer.lex(); + switch (symb.type) { + case ParsedSymbol.TYPE_KEYWORD_EXPLICIT: + info.setFlagExplicit(); + break; + case ParsedSymbol.TYPE_KEYWORD_HAS_OPTIONAL: + info.setFlagHas_optional(); + break; + case ParsedSymbol.TYPE_KEYWORD_HAS_PARAM_NAMES: + info.setFlagHas_paramnames(); + break; + case ParsedSymbol.TYPE_KEYWORD_IGNORE_REST: + info.setFlagIgnore_Rest(); + break; + case ParsedSymbol.TYPE_KEYWORD_NEED_ACTIVATION: + info.setFlagNeed_activation(); + break; + case ParsedSymbol.TYPE_KEYWORD_NEED_ARGUMENTS: + info.setFlagNeed_Arguments(); + break; + case ParsedSymbol.TYPE_KEYWORD_NEED_REST: + info.setFlagNeed_rest(); + break; + case ParsedSymbol.TYPE_KEYWORD_SET_DXNS: + info.setFlagSetsdxns(); + break; + } + continue; + } + if (symb.type == ParsedSymbol.TYPE_KEYWORD_TRY) { + expected(ParsedSymbol.TYPE_KEYWORD_FROM, "From", lexer); + symb = lexer.lex(); + expected(symb, ParsedSymbol.TYPE_IDENTIFIER, "Identifier"); + exceptionsFrom.add((String) symb.value); + expected(ParsedSymbol.TYPE_KEYWORD_TO, "To", lexer); + symb = lexer.lex(); + expected(symb, ParsedSymbol.TYPE_IDENTIFIER, "Identifier"); + exceptionsTo.add((String) symb.value); + expected(ParsedSymbol.TYPE_KEYWORD_TARGET, "Target", lexer); + symb = lexer.lex(); + expected(symb, ParsedSymbol.TYPE_IDENTIFIER, "Identifier"); + exceptionsTargets.add((String) symb.value); + expected(ParsedSymbol.TYPE_KEYWORD_TYPE, "Type", lexer); + ABCException ex = new ABCException(); + ex.type_index = parseMultiName(constants, lexer); + expected(ParsedSymbol.TYPE_KEYWORD_NAME, "Name", lexer); + ex.name_index = parseMultiName(constants, lexer); + exceptions.add(ex); + continue; + } + if (symb.type == ParsedSymbol.TYPE_EXCEPTION_START) { + int exIndex = (Integer) symb.value; + int listIndex = exceptionIndices.indexOf(exIndex); + if (listIndex == -1) { + throw new AVM2ParseException("Undefinex exception index", lexer.yyline()); + } + exceptions.get(listIndex).start = offset; + continue; + } + if (symb.type == ParsedSymbol.TYPE_EXCEPTION_END) { + int exIndex = (Integer) symb.value; + int listIndex = exceptionIndices.indexOf(exIndex); + if (listIndex == -1) { + throw new AVM2ParseException("Undefinex exception index", lexer.yyline()); + } + exceptions.get(listIndex).end = offset; + continue; + } + if (symb.type == ParsedSymbol.TYPE_EXCEPTION_TARGET) { + int exIndex = (Integer) symb.value; + int listIndex = exceptionIndices.indexOf(exIndex); + if (listIndex == -1) { + throw new AVM2ParseException("Undefinex exception index", lexer.yyline()); + } + exceptions.get(listIndex).target = offset; + continue; + } + if (symb.type == ParsedSymbol.TYPE_EOF) { + break; + } + if (symb.type == ParsedSymbol.TYPE_COMMENT) { + if (lastIns != null) { + lastIns.comment = (String) symb.value; + } + continue; + } + if (symb.type == ParsedSymbol.TYPE_INSTRUCTION_NAME) { + if (((String) symb.value).toLowerCase(Locale.ENGLISH).equals("exception")) { + ParsedSymbol exIndex = lexer.lex(); + if (exIndex.type != ParsedSymbol.TYPE_INTEGER) { + throw new AVM2ParseException("Index expected", lexer.yyline()); + } + ParsedSymbol exName = lexer.lex(); + if (exName.type != ParsedSymbol.TYPE_MULTINAME) { + throw new AVM2ParseException("Multiname expected", lexer.yyline()); + } + ParsedSymbol exType = lexer.lex(); + if (exType.type != ParsedSymbol.TYPE_MULTINAME) { + throw new AVM2ParseException("Multiname expected", lexer.yyline()); + } + ABCException ex = new ABCException(); + + ex.name_index = checkMultinameIndex(constants, (int) (long) (Long) exName.value, lexer.yyline()); + ex.type_index = checkMultinameIndex(constants, (int) (long) (Long) exType.value, lexer.yyline()); + exceptions.add(ex); + exceptionIndices.add((int) (long) (Long) exIndex.value); + continue; + } + boolean insFound = false; + for (InstructionDefinition def : AVM2Code.instructionSet) { + if (def != null && def.instructionName.equals((String) symb.value)) { + insFound = true; + List operandsList = new ArrayList<>(); + + for (int i = 0; i < def.operands.length; i++) { + ParsedSymbol parsedOperand = lexer.lex(); + switch (def.operands[i]) { + case AVM2Code.DAT_MULTINAME_INDEX: + lexer.pushback(parsedOperand); + operandsList.add(parseMultiName(constants, lexer)); + /*if (parsedOperand.type == ParsedSymbol.TYPE_MULTINAME) { + operandsList.add(checkMultinameIndex(constants, (int) (long) (Long) parsedOperand.value, lexer.yyline())); + } else { + throw new ParseException("Multiname expected", lexer.yyline()); + }*/ + break; + case AVM2Code.DAT_STRING_INDEX: + if (parsedOperand.type == ParsedSymbol.TYPE_KEYWORD_NULL) { + operandsList.add(0); + } else { + if (parsedOperand.type == ParsedSymbol.TYPE_STRING) { + int sid = constants.getStringId((String) parsedOperand.value); + if (sid == 0) { + if ((missingHandler != null) && (missingHandler.missingString((String) parsedOperand.value))) { + sid = constants.addString((String) parsedOperand.value); + } else { + throw new AVM2ParseException("Unknown String", lexer.yyline()); + } + } + operandsList.add(sid); + } else { + throw new AVM2ParseException("String or null expected", lexer.yyline()); + } + } + break; + case AVM2Code.DAT_INT_INDEX: + + if (parsedOperand.type == ParsedSymbol.TYPE_KEYWORD_NULL) { + operandsList.add(0); + } else { + if (parsedOperand.type == ParsedSymbol.TYPE_INTEGER) { + long intVal = (Long) parsedOperand.value; + int iid = constants.getIntId(intVal); + if (iid == 0) { + if ((missingHandler != null) && (missingHandler.missingInt(intVal))) { + iid = constants.addInt(intVal); + } else { + throw new AVM2ParseException("Unknown int", lexer.yyline()); + } + } + operandsList.add(iid); + } else { + throw new AVM2ParseException("Integer or null expected", lexer.yyline()); + } + } + break; + case AVM2Code.DAT_UINT_INDEX: + if (parsedOperand.type == ParsedSymbol.TYPE_KEYWORD_NULL) { + operandsList.add(0); + } else { + if (parsedOperand.type == ParsedSymbol.TYPE_INTEGER) { + long intVal = (Long) parsedOperand.value; + int iid = constants.getUIntId(intVal); + if (iid == 0) { + if ((missingHandler != null) && (missingHandler.missingUInt(intVal))) { + iid = constants.addUInt(intVal); + } else { + throw new AVM2ParseException("Unknown uint", lexer.yyline()); + } + } + operandsList.add(iid); + } else { + throw new AVM2ParseException("Integer or null expected", lexer.yyline()); + } + } + break; + case AVM2Code.DAT_DOUBLE_INDEX: + if (parsedOperand.type == ParsedSymbol.TYPE_KEYWORD_NULL) { + operandsList.add(0); + } else { + if ((parsedOperand.type == ParsedSymbol.TYPE_INTEGER) || (parsedOperand.type == ParsedSymbol.TYPE_FLOAT)) { + + double doubleVal = 0; + if (parsedOperand.type == ParsedSymbol.TYPE_INTEGER) { + doubleVal = (Long) parsedOperand.value; + } + if (parsedOperand.type == ParsedSymbol.TYPE_FLOAT) { + doubleVal = (Double) parsedOperand.value; + } + int did = constants.getDoubleId(doubleVal); + if (did == 0) { + if ((missingHandler != null) && (missingHandler.missingDouble(doubleVal))) { + did = constants.addDouble(doubleVal); + } else { + throw new AVM2ParseException("Unknown double", lexer.yyline()); + } + } + operandsList.add(did); + } else { + throw new AVM2ParseException("Float or null expected", lexer.yyline()); + } + } + break; + case AVM2Code.DAT_OFFSET: + if (parsedOperand.type == ParsedSymbol.TYPE_IDENTIFIER) { + offsetItems.add(new OffsetItem((String) parsedOperand.value, code.code.size(), i)); + operandsList.add(0); + } else { + throw new AVM2ParseException("Offset expected", lexer.yyline()); + } + break; + case AVM2Code.DAT_CASE_BASEOFFSET: + if (parsedOperand.type == ParsedSymbol.TYPE_IDENTIFIER) { + offsetItems.add(new CaseOffsetItem((String) parsedOperand.value, code.code.size(), i)); + operandsList.add(0); + } else { + throw new AVM2ParseException("Offset expected", lexer.yyline()); + } + break; + case AVM2Code.OPT_CASE_OFFSETS: + + if (parsedOperand.type == ParsedSymbol.TYPE_INTEGER) { + int patCount = (int) (long) (Long) parsedOperand.value; + operandsList.add(patCount); + + for (int c = 0; c <= patCount; c++) { + parsedOperand = lexer.lex(); + if (parsedOperand.type == ParsedSymbol.TYPE_IDENTIFIER) { + offsetItems.add(new CaseOffsetItem((String) parsedOperand.value, code.code.size(), i + (c + 1))); + operandsList.add(0); + } else { + throw new AVM2ParseException("Offset expected", lexer.yyline()); + } + } + } else { + throw new AVM2ParseException("Case count expected", lexer.yyline()); + } + break; + case AVM2Code.OPT_BYTE: + if (parsedOperand.type == ParsedSymbol.TYPE_INTEGER) { + long val = (long) (Long) parsedOperand.value; + if (val < Byte.MIN_VALUE || val > Byte.MAX_VALUE) { + throw new AVM2ParseException("Byte value expected (" + Byte.MIN_VALUE + " to " + Byte.MAX_VALUE + "). Use pushshort or pushint to push larger values", lexer.yyline()); + } + operandsList.add((int) val); + } else { + throw new AVM2ParseException("Integer expected", lexer.yyline()); + } + break; + default: + if (parsedOperand.type == ParsedSymbol.TYPE_INTEGER) { + long val = (long) (Long) parsedOperand.value; + if (def instanceof PushShortIns) { + if (val < Short.MIN_VALUE || val > Short.MAX_VALUE) { + throw new AVM2ParseException("Short value expected (" + Short.MIN_VALUE + " to " + Short.MAX_VALUE + "). Use pushint to push larger values", lexer.yyline()); + } + } + operandsList.add((int) val); + } else { + throw new AVM2ParseException("Integer expected", lexer.yyline()); + } + } + } + + int[] operands = new int[operandsList.size()]; + for (int i = 0; i < operandsList.size(); i++) { + operands[i] = operandsList.get(i); + } + lastIns = new AVM2Instruction(offset, def, operands); + code.code.add(lastIns); + offset += lastIns.getBytesLength(); + break; + } + } + if (symb.value.toString().toLowerCase().equals("ffdec_deobfuscatepop")) { + lastIns = new AVM2Instruction(offset, new DeobfuscatePopIns(), null); + code.code.add(lastIns); + offset += lastIns.getBytesLength(); + insFound = true; + } + if (!insFound) { + throw new AVM2ParseException("Invalid instruction name:" + (String) symb.value, lexer.yyline()); + } + } else if (symb.type == ParsedSymbol.TYPE_LABEL) { + labelItems.add(new LabelItem((String) symb.value, offset)); + + } else { + throw new AVM2ParseException("Unexpected symbol", lexer.yyline()); + } + } while (symb.type != ParsedSymbol.TYPE_EOF); + + code.compact(); + for (LabelItem li : labelItems) { + int ind; + ind = exceptionsFrom.indexOf(li.label); + if (ind > -1) { + exceptions.get(ind).start = li.offset; + } + + ind = exceptionsTo.indexOf(li.label); + if (ind > -1) { + exceptions.get(ind).end = li.offset; + } + + ind = exceptionsTargets.indexOf(li.label); + if (ind > -1) { + exceptions.get(ind).target = li.offset; + } + } + + for (OffsetItem oi : offsetItems) { + for (LabelItem li : labelItems) { + if (Thread.currentThread().isInterrupted()) { + throw new InterruptedException(); + } + if (oi.label.equals(li.label)) { + AVM2Instruction ins = code.code.get((int) oi.insPosition); + int relOffset; + if (oi instanceof CaseOffsetItem) { + relOffset = li.offset - (int) ins.offset; + } else { + relOffset = li.offset - ((int) ins.offset + ins.getBytesLength()); + } + ins.operands[oi.insOperandIndex] = relOffset; + } + } + } + body.exceptions = new ABCException[exceptions.size()]; + for (int e = 0; e < exceptions.size(); e++) { + body.exceptions[e] = exceptions.get(e); + } + + info.param_types = new int[paramTypes.size()]; + for (int i = 0; i < paramTypes.size(); i++) { + info.param_types[i] = paramTypes.get(i); + } + + if (info.flagHas_paramnames()) { + info.paramNames = new int[paramNames.size()]; + for (int i = 0; i < paramNames.size(); i++) { + info.paramNames[i] = paramNames.get(i); + } + } + + if (info.flagHas_optional()) { + info.optional = new ValueKind[optional.size()]; + for (int i = 0; i < optional.size(); i++) { + info.optional[i] = optional.get(i); + } + } + return code; + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/types/ABCException.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/types/ABCException.java index 2cc18ba4d..bc58992b6 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/types/ABCException.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/types/ABCException.java @@ -1,78 +1,78 @@ -/* - * Copyright (C) 2010-2015 JPEXS, All rights reserved. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3.0 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. - */ -package com.jpexs.decompiler.flash.abc.types; - -import com.jpexs.decompiler.flash.abc.avm2.AVM2Code; -import com.jpexs.decompiler.flash.abc.avm2.AVM2ConstantPool; -import com.jpexs.decompiler.flash.abc.avm2.ConvertException; -import com.jpexs.helpers.Helper; -import java.io.Serializable; -import java.util.List; - -public class ABCException implements Serializable, Cloneable { - - public int start; - - public int end; - - public int target; - - public int type_index; - - public int name_index; - - @Override - public String toString() { - return "Exception: startServer=" + Helper.formatAddress(start) + " end=" + Helper.formatAddress(end) + " target=" + target + " type_index=" + type_index + " name_index=" + name_index; - } - - public String toString(AVM2ConstantPool constants, List fullyQualifiedNames) { - return "Exception: startServer=" + Helper.formatAddress(start) + " end=" + Helper.formatAddress(end) + " target=" + target + " type=\"" + getTypeName(constants, fullyQualifiedNames) + "\" name=\"" + getVarName(constants, fullyQualifiedNames) + "\""; - } - - public String toString(AVM2ConstantPool constants, AVM2Code code, List fullyQualifiedNames) { - try { - return "Exception: startServer=" + code.adr2pos(start) + ":" + code.code.get(code.adr2pos(start)).toStringNoAddress(constants, fullyQualifiedNames) + " end=" + code.adr2pos(end) + ":" + code.code.get(code.adr2pos(end)).toStringNoAddress(constants, fullyQualifiedNames) + " target=" + code.adr2pos(target) + ":" + code.code.get(code.adr2pos(target)).toStringNoAddress(constants, fullyQualifiedNames) + " type=\"" + getTypeName(constants, fullyQualifiedNames) + "\" name=\"" + getVarName(constants, fullyQualifiedNames) + "\""; - } catch (ConvertException ex) { - return ""; - } - } - - public boolean isFinally() { - return (name_index == 0) && (type_index == 0); - } - - public String getVarName(AVM2ConstantPool constants, List fullyQualifiedNames) { - if (name_index == 0) { - return ""; - } - return constants.getMultiname(name_index).getName(constants, fullyQualifiedNames, false); - } - - public String getTypeName(AVM2ConstantPool constants, List fullyQualifiedNames) { - if (type_index == 0) { - return "*"; - } - return constants.getMultiname(type_index).getName(constants, fullyQualifiedNames, false); - } - - @Override - public ABCException clone() throws CloneNotSupportedException { - ABCException ret = (ABCException) super.clone(); - return ret; - } -} +/* + * Copyright (C) 2010-2015 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.flash.abc.types; + +import com.jpexs.decompiler.flash.abc.avm2.AVM2Code; +import com.jpexs.decompiler.flash.abc.avm2.AVM2ConstantPool; +import com.jpexs.decompiler.flash.abc.avm2.ConvertException; +import com.jpexs.helpers.Helper; +import java.io.Serializable; +import java.util.List; + +public class ABCException implements Serializable, Cloneable { + + public int start; + + public int end; + + public int target; + + public int type_index; + + public int name_index; + + @Override + public String toString() { + return "Exception: startServer=" + Helper.formatAddress(start) + " end=" + Helper.formatAddress(end) + " target=" + target + " type_index=" + type_index + " name_index=" + name_index; + } + + public String toString(AVM2ConstantPool constants, List fullyQualifiedNames) { + return "Exception: startServer=" + Helper.formatAddress(start) + " end=" + Helper.formatAddress(end) + " target=" + target + " type=\"" + getTypeName(constants, fullyQualifiedNames) + "\" name=\"" + getVarName(constants, fullyQualifiedNames) + "\""; + } + + public String toString(AVM2ConstantPool constants, AVM2Code code, List fullyQualifiedNames) { + try { + return "Exception: startServer=" + code.adr2pos(start) + ":" + code.code.get(code.adr2pos(start)).toStringNoAddress(constants, fullyQualifiedNames) + " end=" + code.adr2pos(end) + ":" + code.code.get(code.adr2pos(end)).toStringNoAddress(constants, fullyQualifiedNames) + " target=" + code.adr2pos(target) + ":" + code.code.get(code.adr2pos(target)).toStringNoAddress(constants, fullyQualifiedNames) + " type=\"" + getTypeName(constants, fullyQualifiedNames) + "\" name=\"" + getVarName(constants, fullyQualifiedNames) + "\""; + } catch (ConvertException ex) { + return ""; + } + } + + public boolean isFinally() { + return (name_index == 0) && (type_index == 0); + } + + public String getVarName(AVM2ConstantPool constants, List fullyQualifiedNames) { + if (name_index == 0) { + return ""; + } + return constants.getMultiname(name_index).getName(constants, fullyQualifiedNames, false); + } + + public String getTypeName(AVM2ConstantPool constants, List fullyQualifiedNames) { + if (type_index == 0) { + return "*"; + } + return constants.getMultiname(type_index).getName(constants, fullyQualifiedNames, false); + } + + @Override + public ABCException clone() throws CloneNotSupportedException { + ABCException ret = (ABCException) super.clone(); + return ret; + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineEditTextTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineEditTextTag.java index a9531d98a..7ad9b8ac9 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineEditTextTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineEditTextTag.java @@ -1,1146 +1,1146 @@ -/* - * Copyright (C) 2010-2015 JPEXS, All rights reserved. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3.0 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. - */ -package com.jpexs.decompiler.flash.tags; - -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.SWFInputStream; -import com.jpexs.decompiler.flash.SWFOutputStream; -import com.jpexs.decompiler.flash.configuration.Configuration; -import com.jpexs.decompiler.flash.exporters.commonshape.ExportRectangle; -import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; -import com.jpexs.decompiler.flash.exporters.commonshape.SVGExporter; -import com.jpexs.decompiler.flash.helpers.HighlightedText; -import com.jpexs.decompiler.flash.helpers.HighlightedTextWriter; -import com.jpexs.decompiler.flash.helpers.hilight.HighlightSpecialType; -import com.jpexs.decompiler.flash.tags.base.BoundedTag; -import com.jpexs.decompiler.flash.tags.base.FontTag; -import com.jpexs.decompiler.flash.tags.base.MissingCharacterHandler; -import com.jpexs.decompiler.flash.tags.base.RenderContext; -import com.jpexs.decompiler.flash.tags.base.TextTag; -import com.jpexs.decompiler.flash.tags.dynamictext.CharacterWithStyle; -import com.jpexs.decompiler.flash.tags.dynamictext.DynamicTextModel; -import com.jpexs.decompiler.flash.tags.dynamictext.GlyphCharacter; -import com.jpexs.decompiler.flash.tags.dynamictext.Paragraph; -import com.jpexs.decompiler.flash.tags.dynamictext.SameStyleTextRecord; -import com.jpexs.decompiler.flash.tags.dynamictext.TextStyle; -import com.jpexs.decompiler.flash.tags.dynamictext.Word; -import com.jpexs.decompiler.flash.tags.enums.TextRenderMode; -import com.jpexs.decompiler.flash.tags.text.ParsedSymbol; -import com.jpexs.decompiler.flash.tags.text.TextAlign; -import com.jpexs.decompiler.flash.tags.text.TextLexer; -import com.jpexs.decompiler.flash.tags.text.TextParseException; -import com.jpexs.decompiler.flash.types.BasicType; -import com.jpexs.decompiler.flash.types.ColorTransform; -import com.jpexs.decompiler.flash.types.DynamicTextGlyphEntry; -import com.jpexs.decompiler.flash.types.MATRIX; -import com.jpexs.decompiler.flash.types.RECT; -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.types.annotations.Conditional; -import com.jpexs.decompiler.flash.types.annotations.SWFType; -import com.jpexs.helpers.ByteArrayRange; -import com.jpexs.helpers.SerializableImage; -import com.jpexs.helpers.utf8.Utf8Helper; -import java.awt.Color; -import java.awt.Font; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.StringReader; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Set; -import java.util.Stack; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.parsers.SAXParser; -import javax.xml.parsers.SAXParserFactory; -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; -import org.xml.sax.helpers.DefaultHandler; - -/** - * - * @author JPEXS - */ -public class DefineEditTextTag extends TextTag { - - public static final int ID = 37; - - public static final String NAME = "DefineEditText"; - - @SWFType(BasicType.UI16) - public int characterID; - - public RECT bounds; - - public boolean hasText; - - public boolean wordWrap; - - public boolean multiline; - - public boolean password; - - public boolean readOnly; - - public boolean hasTextColor; - - public boolean hasMaxLength; - - public boolean hasFont; - - public boolean hasFontClass; - - public boolean autoSize; - - public boolean hasLayout; - - public boolean noSelect; - - public boolean border; - - public boolean wasStatic; - - public boolean html; - - public boolean useOutlines; - - @SWFType(BasicType.UI16) - @Conditional("hasFont") - public int fontId; - - @Conditional("hasFontClass") - public String fontClass; - - @SWFType(BasicType.UI16) - @Conditional("hasFont") - public int fontHeight; - - @Conditional("hasTextColor") - public RGBA textColor; - - @SWFType(BasicType.UI16) - @Conditional("hasMaxLength") - public int maxLength; - - @SWFType(BasicType.UI8) - @Conditional("hasLayout") - public int align; - - @SWFType(BasicType.UI16) - @Conditional("hasLayout") - public int leftMargin; - - @SWFType(BasicType.UI16) - @Conditional("hasLayout") - public int rightMargin; - - @SWFType(BasicType.UI16) - @Conditional("hasLayout") - public int indent; - - @SWFType(BasicType.SI16) - @Conditional("hasLayout") - public int leading; - - public String variableName; - - @Conditional("hasText") - public String initialText; - - /** - * Constructor - * - * @param swf - */ - public DefineEditTextTag(SWF swf) { - super(swf, ID, NAME, null); - characterID = swf.getNextCharacterId(); - bounds = new RECT(); - variableName = ""; - } - - /** - * Constructor - * - * @param sis - * @param data - * @throws IOException - */ - public DefineEditTextTag(SWFInputStream sis, ByteArrayRange data) throws IOException { - super(sis.getSwf(), ID, NAME, data); - readData(sis, data, 0, false, false, false); - } - - @Override - public final void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException { - characterID = sis.readUI16("characterID"); - bounds = sis.readRECT("bounds"); - hasText = sis.readUB(1, "hasText") == 1; - wordWrap = sis.readUB(1, "wordWrap") == 1; - multiline = sis.readUB(1, "multiline") == 1; - password = sis.readUB(1, "password") == 1; - readOnly = sis.readUB(1, "readOnly") == 1; - hasTextColor = sis.readUB(1, "hasTextColor") == 1; - hasMaxLength = sis.readUB(1, "hasMaxLength") == 1; - hasFont = sis.readUB(1, "hasFont") == 1; - hasFontClass = sis.readUB(1, "hasFontClass") == 1; - autoSize = sis.readUB(1, "autoSize") == 1; - hasLayout = sis.readUB(1, "hasLayout") == 1; - noSelect = sis.readUB(1, "noSelect") == 1; - border = sis.readUB(1, "border") == 1; - wasStatic = sis.readUB(1, "wasStatic") == 1; - html = sis.readUB(1, "html") == 1; - useOutlines = sis.readUB(1, "useOutlines") == 1; - if (hasFont) { - fontId = sis.readUI16("fontId"); - } - if (hasFontClass) { - fontClass = sis.readString("fontClass"); - } - if (hasFont) { - fontHeight = sis.readUI16("fontHeight"); - } - if (hasTextColor) { - textColor = sis.readRGBA("textColor"); - } - if (hasMaxLength) { - maxLength = sis.readUI16("maxLength"); - } - if (hasLayout) { - align = sis.readUI8("align"); //0 left, 1 right, 2 center, 3 justify - leftMargin = sis.readUI16("leftMargin"); - rightMargin = sis.readUI16("rightMargin"); - indent = sis.readUI16("indent"); - leading = sis.readSI16("leading"); - } - variableName = sis.readString("variableName"); - if (hasText) { - initialText = sis.readString("initialText"); - } - - } - - /** - * Gets data bytes - * - * @param sos SWF output stream - * @throws java.io.IOException - */ - @Override - public void getData(SWFOutputStream sos) throws IOException { - sos.writeUI16(characterID); - sos.writeRECT(bounds); - sos.writeUB(1, hasText ? 1 : 0); - sos.writeUB(1, wordWrap ? 1 : 0); - sos.writeUB(1, multiline ? 1 : 0); - sos.writeUB(1, password ? 1 : 0); - sos.writeUB(1, readOnly ? 1 : 0); - sos.writeUB(1, hasTextColor ? 1 : 0); - sos.writeUB(1, hasMaxLength ? 1 : 0); - sos.writeUB(1, hasFont ? 1 : 0); - sos.writeUB(1, hasFontClass ? 1 : 0); - sos.writeUB(1, autoSize ? 1 : 0); - sos.writeUB(1, hasLayout ? 1 : 0); - sos.writeUB(1, noSelect ? 1 : 0); - sos.writeUB(1, border ? 1 : 0); - sos.writeUB(1, wasStatic ? 1 : 0); - sos.writeUB(1, html ? 1 : 0); - sos.writeUB(1, useOutlines ? 1 : 0); - if (hasFont) { - sos.writeUI16(fontId); - } - if (hasFontClass) { - sos.writeString(fontClass); - } - if (hasFont) { - sos.writeUI16(fontHeight); - } - if (hasTextColor) { - sos.writeRGBA(textColor); - } - if (hasMaxLength) { - sos.writeUI16(maxLength); - } - if (hasLayout) { - sos.writeUI8(align); - sos.writeUI16(leftMargin); - sos.writeUI16(rightMargin); - sos.writeUI16(indent); - sos.writeSI16(leading); - } - sos.writeString(variableName); - if (hasText) { - sos.writeString(initialText); - } - } - - @Override - public RECT getBounds() { - return bounds; - } - - @Override - public MATRIX getTextMatrix() { - MATRIX matrix = new MATRIX(); - matrix.translateX = bounds.Xmin; - matrix.translateY = bounds.Ymin; - return matrix; - } - - @Override - public void setBounds(RECT r) { - bounds = r; - } - - private String stripTags(String inp) { - boolean intag = false; - String outp = ""; - inp = inp.replaceAll("
", "\r\n"); - for (int i = 0; i < inp.length(); ++i) { - if (!intag && inp.charAt(i) == '<') { - intag = true; - continue; - } - if (intag && inp.charAt(i) == '>') { - intag = false; - continue; - } - if (!intag) { - outp += inp.charAt(i); - } - } - return outp; - } - - private String entitiesReplace(String s) { - s = s.replace("<", "<"); - s = s.replace(">", ">"); - s = s.replace("&", "&"); - s = s.replace(""", "\""); - return s; - } - - @Override - public List getTexts() { - String ret = ""; - if (hasText) { - ret = initialText; - } - if (html) { - ret = stripTags(ret); - ret = entitiesReplace(ret); - } - return Arrays.asList(ret); - } - - private List getTextWithStyle() { - String str = ""; - TextStyle style = new TextStyle(); - style.font = swf.getFont(fontId); - style.fontHeight = fontHeight; - style.fontLeading = leading; - if (hasTextColor) { - style.textColor = textColor; - } - if (hasText) { - str = initialText; - } - final List ret = new ArrayList<>(); - if (html) { - SAXParserFactory factory = SAXParserFactory.newInstance(); - SAXParser saxParser; - final Stack styles = new Stack<>(); - styles.add(style); - try { - saxParser = factory.newSAXParser(); - DefaultHandler handler = new DefaultHandler() { - - @Override - public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { - TextStyle style = styles.peek(); - switch (qName) { - case "p": - // todo: parse the following attribute: - // align - break; - case "b": - style = style.clone(); - style.bold = true; - styles.add(style); - break; - case "i": - style = style.clone(); - style.italic = true; - styles.add(style); - break; - case "u": - style = style.clone(); - style.underlined = true; - styles.add(style); - break; - case "font": - style = style.clone(); - String color = attributes.getValue("color"); - if (color != null) { - if (color.startsWith("#")) { - style.textColor = new RGBA(Color.decode(color)); - } - } - String size = attributes.getValue("size"); - if (size != null && size.length() > 0) { - char firstChar = size.charAt(0); - if (firstChar != '+' && firstChar != '-') { - int fontSize = Integer.parseInt(size); - style.fontHeight = (int) Math.round(fontSize * (style.font == null ? 1 : style.font.getDivider())); - style.fontLeading = leading; - } else { - // todo: parse relative sizes - } - } - String face = attributes.getValue("face"); - { - if (face != null && face.length() > 0) { - style.fontFace = face; - } - } - // todo: parse the following attributes: letterSpacing, kerning - styles.add(style); - break; - case "br": - case "sbr": // what's this? - CharacterWithStyle cs = new CharacterWithStyle(); - cs.character = '\n'; - cs.style = style; - ret.add(cs); - break; - } - //ret = entitiesReplace(ret); - } - - @Override - public void endElement(String uri, String localName, String qName) throws SAXException { - switch (qName) { - case "b": - case "i": - case "u": - case "font": - styles.pop(); - break; - case "p": - TextStyle style = styles.peek(); - CharacterWithStyle cs = new CharacterWithStyle(); - cs.character = '\n'; - cs.style = style; - ret.add(cs); - break; - } - } - - @Override - public void characters(char[] ch, int start, int length) throws SAXException { - String txt = new String(ch, start, length); - TextStyle style = styles.peek(); - addCharacters(ret, txt, style); - } - }; - str = " \n" - + "]>" + str + ""; - saxParser.parse(new ByteArrayInputStream(str.getBytes(Utf8Helper.charset)), handler); - } catch (ParserConfigurationException | SAXException | IOException ex) { - Logger.getLogger(DefineEditTextTag.class.getName()).log(Level.SEVERE, null, ex); - } - } else { - addCharacters(ret, str, style); - } - return ret; - } - - private void addCharacters(List list, String str, TextStyle style) { - for (int i = 0; i < str.length(); i++) { - char ch = str.charAt(i); - CharacterWithStyle cs = new CharacterWithStyle(); - cs.character = ch; - cs.style = style; - list.add(cs); - } - } - - @Override - public List getFontIds() { - List ret = new ArrayList<>(); - ret.add(fontId); - return ret; - } - - @Override - public HighlightedText getFormattedText() { - HighlightedTextWriter writer = new HighlightedTextWriter(Configuration.getCodeFormatting(), true); - writer.append("["); - String[] alignNames = {"left", "right", "center", "justify"}; - String alignment; - if (align < alignNames.length) { - alignment = alignNames[align]; - } else { - alignment = "unknown"; - } - writer.newLine(); - writer.append("xmin " + bounds.Xmin).newLine(); - writer.append("ymin " + bounds.Ymin).newLine(); - writer.append("xmax " + bounds.Xmax).newLine(); - writer.append("ymax " + bounds.Ymax).newLine(); - if (wordWrap) { - writer.append("wordwrap 1").newLine(); - } - if (multiline) { - writer.append("multiline 1").newLine(); - } - if (password) { - writer.append("password 1").newLine(); - } - if (readOnly) { - writer.append("readonly 1").newLine(); - } - if (autoSize) { - writer.append("autosize 1").newLine(); - } - if (noSelect) { - writer.append("noselect 1").newLine(); - } - if (border) { - writer.append("border 1").newLine(); - } - if (wasStatic) { - writer.append("wasstatic 1").newLine(); - } - if (html) { - writer.append("html 1").newLine(); - } - if (useOutlines) { - writer.append("useoutlines 1").newLine(); - } - if (hasFont) { - writer.append("font " + fontId).newLine(); - writer.append("height " + fontHeight).newLine(); - } - if (hasTextColor) { - writer.append("color " + textColor.toHexARGB()).newLine(); - } - if (hasFontClass) { - writer.append("fontclass " + fontClass).newLine(); - } - if (hasMaxLength) { - writer.append("maxlength " + maxLength).newLine(); - } - writer.append("align " + alignment).newLine(); - if (hasLayout) { - writer.append("leftmargin " + leftMargin).newLine(); - writer.append("rightmargin " + rightMargin).newLine(); - writer.append("indent " + indent).newLine(); - writer.append("leading " + leading).newLine(); - } - if (!variableName.isEmpty()) { - writer.append("variablename " + variableName).newLine(); - } - writer.append("]"); - if (hasText) { - String text = initialText.replace("\\", "\\\\").replace("[", "\\[").replace("]", "\\]"); - writer.hilightSpecial(text, HighlightSpecialType.TEXT); - } - return new HighlightedText(writer); - } - - @Override - public boolean setFormattedText(MissingCharacterHandler missingCharHandler, String formattedText, String[] texts) throws TextParseException { - try { - TextLexer lexer = new TextLexer(new StringReader(formattedText)); - ParsedSymbol s = null; - formattedText = ""; - RECT bounds = new RECT(this.bounds); - boolean wordWrap = false; - boolean multiline = false; - boolean password = false; - boolean readOnly = false; - boolean autoSize = false; - boolean noSelect = false; - boolean border = false; - boolean wasStatic = false; - boolean html = false; - boolean useOutlines = false; - int fontId = -1; - int fontHeight = -1; - String fontClass = null; - RGBA textColor = null; - int maxLength = -1; - int align = -1; - int leftMargin = -1; - int rightMargin = -1; - int indent = -1; - int leading = -1; - String variableName = null; - - int textIdx = 0; - while ((s = lexer.yylex()) != null) { - switch (s.type) { - case PARAMETER: - String paramName = (String) s.values[0]; - String paramValue = (String) s.values[1]; - switch (paramName) { - case "xmin": - try { - bounds.Xmin = Integer.parseInt(paramValue); - } catch (NumberFormatException nfe) { - throw new TextParseException("Invalid xmin value. Number expected. Found: " + paramValue, lexer.yyline()); - } - break; - case "ymin": - try { - bounds.Ymin = Integer.parseInt(paramValue); - } catch (NumberFormatException nfe) { - throw new TextParseException("Invalid ymin value. Number expected. Found: " + paramValue, lexer.yyline()); - } - break; - case "xmax": - try { - bounds.Xmax = Integer.parseInt(paramValue); - } catch (NumberFormatException nfe) { - throw new TextParseException("Invalid xmax value. Number expected. Found: " + paramValue, lexer.yyline()); - } - break; - case "ymax": - try { - bounds.Ymax = Integer.parseInt(paramValue); - } catch (NumberFormatException nfe) { - throw new TextParseException("Invalid ymax value. Number expected. Found: " + paramValue, lexer.yyline()); - } - break; - case "wordwrap": - if (paramValue.equals("1")) { - wordWrap = true; - } - break; - case "multiline": - if (paramValue.equals("1")) { - multiline = true; - } - break; - case "password": - if (paramValue.equals("1")) { - password = true; - } - break; - case "readonly": - if (paramValue.equals("1")) { - readOnly = true; - } - break; - case "autosize": - if (paramValue.equals("1")) { - autoSize = true; - } - break; - case "noselect": - if (paramValue.equals("1")) { - noSelect = true; - } - break; - case "border": - if (paramValue.equals("1")) { - border = true; - } - break; - case "wasstatic": - if (paramValue.equals("1")) { - wasStatic = true; - } - break; - case "html": - if (paramValue.equals("1")) { - html = true; - } - break; - case "useoutlines": - if (paramValue.equals("1")) { - useOutlines = true; - } - break; - case "font": - try { - fontId = Integer.parseInt(paramValue); - - FontTag ft = swf.getFont(fontId); - if (ft == null) { - throw new TextParseException("Font not found.", lexer.yyline()); - } - } catch (NumberFormatException ne) { - throw new TextParseException("Invalid font value. Number expected. Found: " + paramValue, lexer.yyline()); - } - break; - case "fontclass": - fontClass = paramValue; - break; - case "height": - try { - fontHeight = Integer.parseInt(paramValue); - } catch (NumberFormatException ne) { - throw new TextParseException("Invalid height value. Number expected. Found: " + paramValue, lexer.yyline()); - } - break; - case "color": - Matcher m = Pattern.compile("#([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])").matcher(paramValue); - if (m.matches()) { - textColor = new RGBA(Integer.parseInt(m.group(2), 16), Integer.parseInt(m.group(3), 16), Integer.parseInt(m.group(4), 16), Integer.parseInt(m.group(1), 16)); - } else { - throw new TextParseException("Invalid color. Valid format is #aarrggbb. Found: " + paramValue, lexer.yyline()); - } - break; - case "maxlength": - try { - maxLength = Integer.parseInt(paramValue); - } catch (NumberFormatException ne) { - throw new TextParseException("Invalid maxLength value. Number expected. Found: " + paramValue, lexer.yyline()); - } - break; - case "align": - switch (paramValue) { - case "left": - align = 0; - break; - case "right": - align = 1; - break; - case "center": - align = 2; - break; - case "justify": - align = 3; - break; - default: - throw new TextParseException("Invalid align value. Expected one of: left,right,center or justify. Found: " + paramValue, lexer.yyline()); - } - break; - case "leftmargin": - try { - leftMargin = Integer.parseInt(paramValue); - } catch (NumberFormatException ne) { - throw new TextParseException("Invalid leftmargin value. Number expected. Found: " + paramValue, lexer.yyline()); - } - break; - case "rightmargin": - try { - rightMargin = Integer.parseInt(paramValue); - } catch (NumberFormatException ne) { - throw new TextParseException("Invalid rightmargin value. Number expected. Found: " + paramValue, lexer.yyline()); - } - break; - case "indent": - try { - indent = Integer.parseInt(paramValue); - } catch (NumberFormatException ne) { - throw new TextParseException("Invalid indent value. Number expected. Found: " + paramValue, lexer.yyline()); - } - break; - case "leading": - try { - leading = Integer.parseInt(paramValue); - } catch (NumberFormatException ne) { - throw new TextParseException("Invalid leading value. Number expected. Found: " + paramValue, lexer.yyline()); - } - break; - case "variablename": - variableName = paramValue; - break; - default: - throw new TextParseException("Unrecognized parameter name: " + paramName, lexer.yyline()); - } - break; - case TEXT: - String s2 = (String) s.values[0]; - if (s2 == null) { - s2 = ""; - } - - formattedText += (texts == null || textIdx >= texts.length) ? s2 : texts[textIdx++]; - formattedText = formattedText.replace("\r\n", "\r"); - break; - } - } - - setModified(true); - this.bounds = bounds; - if (formattedText.length() > 0) { - initialText = formattedText; - this.hasText = true; - } else { - this.hasText = false; - } - this.wordWrap = wordWrap; - this.multiline = multiline; - this.password = password; - this.readOnly = readOnly; - this.noSelect = noSelect; - this.border = border; - this.wasStatic = wasStatic; - this.html = html; - this.useOutlines = useOutlines; - if (textColor != null) { - hasTextColor = true; - this.textColor = textColor; - } - if (maxLength > -1) { - this.maxLength = maxLength; - hasMaxLength = true; - } - if (fontId > -1) { - this.fontId = fontId; - } - if (fontHeight > -1) { - this.fontHeight = fontHeight; - } - if (fontClass != null) { - this.fontClass = fontClass; - hasFontClass = true; - } - this.autoSize = autoSize; - this.align = align; - if ((leftMargin > -1) - || (rightMargin > -1) - || (indent > -1) - || (leading > -1)) { - this.leftMargin = leftMargin; - this.rightMargin = rightMargin; - this.indent = indent; - this.leading = leading; - hasLayout = true; - } - if (variableName == null) { - variableName = ""; - } - this.variableName = variableName; - - } catch (IOException ex) { - Logger.getLogger(DefineEditTextTag.class.getName()).log(Level.SEVERE, null, ex); - return false; - } - - return true; - } - - @Override - public void updateTextBounds() { - } - - @Override - public boolean alignText(TextAlign textAlign) { - return true; - } - - @Override - public boolean translateText(int diff) { - return true; - } - - @Override - public RECT getRect(Set added) { - return bounds; - } - - @Override - public int getCharacterId() { - return characterID; - } - - @Override - public void setCharacterId(int characterId) { - this.characterID = characterId; - } - - @Override - public void getNeededCharacters(Set needed) { - if (hasFont) { - needed.add(fontId); - } - } - - @Override - public boolean replaceCharacter(int oldCharacterId, int newCharacterId) { - if (fontId == oldCharacterId) { - fontId = newCharacterId; - setModified(true); - return true; - } - return false; - } - - @Override - public boolean removeCharacter(int characterId) { - if (fontId == characterId) { - hasFont = false; - fontId = 0; - setModified(true); - return true; - } - return false; - } - - @Override - public int getUsedParameters() { - return 0; - } - - @Override - public void toImage(int frame, int time, int ratio, RenderContext renderContext, SerializableImage image, Matrix transformation, ColorTransform colorTransform) { - render(TextRenderMode.BITMAP, image, null, null, transformation, colorTransform, 1); - } - - @Override - public void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level, double zoom) { - render(TextRenderMode.SVG, null, exporter, null, new Matrix(), colorTransform, zoom); - } - - @Override - public void toHtmlCanvas(StringBuilder result, double unitDivisor) { - render(TextRenderMode.HTML5_CANVAS, null, null, result, new Matrix(), new ColorTransform(), unitDivisor); - } - - private void render(TextRenderMode renderMode, SerializableImage image, SVGExporter svgExporter, StringBuilder htmlCanvasBuilder, Matrix transformation, ColorTransform colorTransform, double zoom) { - if (border) { - // border is always black, fill color is always white? - RGB borderColor = new RGBA(Color.black); - RGB fillColor = new RGBA(Color.white); - switch (renderMode) { - case BITMAP: - drawBorder(swf, image, borderColor, fillColor, getRect(), getTextMatrix(), transformation, colorTransform); - break; - case HTML5_CANVAS: - drawBorderHtmlCanvas(swf, htmlCanvasBuilder, borderColor, fillColor, getRect(), getTextMatrix(), colorTransform, zoom); - break; - case SVG: - drawBorderSVG(swf, svgExporter, borderColor, fillColor, getRect(), getTextMatrix(), colorTransform, zoom); - break; - } - } - if (hasText) { - DynamicTextModel textModel = new DynamicTextModel(); - List txt = getTextWithStyle(); - TextStyle lastStyle = null; - char prevChar = 0; - boolean lastWasWhiteSpace = false; - for (int i = 0; i < txt.size(); i++) { - CharacterWithStyle cs = txt.get(i); - char c = cs.character; - if (c != '\r' && c != '\n') { - // create new SameStyleTextRecord for all words and all diffrent style text parts - if (lastWasWhiteSpace && !Character.isWhitespace(c)) { - textModel.newWord(); - lastWasWhiteSpace = false; - } - if (cs.style != lastStyle) { - lastStyle = cs.style; - textModel.style = lastStyle; - textModel.newRecord(); - } - Character nextChar = null; - if (i + 1 < txt.size()) { - nextChar = txt.get(i + 1).character; - } - int advance; - FontTag font = lastStyle.font; - DynamicTextGlyphEntry ge = new DynamicTextGlyphEntry(); - ge.fontFace = lastStyle.fontFace; - ge.fontStyle = (lastStyle.bold ? Font.BOLD : 0) | (lastStyle.italic ? Font.ITALIC : 0); - ge.character = c; - ge.glyphIndex = font == null ? -1 : font.charToGlyph(c); - if (font != null && font.hasLayout()) { - int kerningAdjustment = 0; - if (nextChar != null) { - kerningAdjustment = font.getCharKerningAdjustment(c, nextChar); - kerningAdjustment /= font.getDivider(); - } - advance = (int) Math.round(Math.round((double) lastStyle.fontHeight * (font.getGlyphAdvance(ge.glyphIndex) + kerningAdjustment) / (font.getDivider() * 1024.0))); - } else { - String fontName = lastStyle.fontFace != null ? lastStyle.fontFace : FontTag.defaultFontName; - int fontStyle = font == null ? ge.fontStyle : font.getFontStyle(); - advance = (int) Math.round(SWF.unitDivisor * FontTag.getSystemFontAdvance(fontName, fontStyle, (int) (lastStyle.fontHeight / SWF.unitDivisor), c, nextChar)); - } - ge.glyphAdvance = advance; - textModel.addGlyph(c, ge); - if (Character.isWhitespace(c)) { - lastWasWhiteSpace = true; - } - } else { - if (multiline) { - textModel.newParagraph(); - } - } - prevChar = c; - } - - textModel.calculateTextWidths(); - List> lines; - if (multiline && wordWrap) { - lines = new ArrayList<>(); - for (Paragraph paragraph : textModel.paragraphs) { - List line = new ArrayList<>(); - int lineLength = 0; - for (Word word : paragraph.words) { - if (lineLength + word.width <= bounds.getWidth()) { - line.addAll(word.records); - lineLength += word.width; - } else { - lines.add(line); - line = new ArrayList<>(); - line.addAll(word.records); - lineLength = 0; - } - } - if (!line.isEmpty()) { - lines.add(line); - } - } - } else { - lines = new ArrayList<>(); - for (Paragraph paragraph : textModel.paragraphs) { - List line = new ArrayList<>(); - for (Word word : paragraph.words) { - for (SameStyleTextRecord tr : word.records) { - line.add(tr); - } - } - lines.add(line); - } - } - - // remove spaces after last word - for (List line : lines) { - boolean removed = true; - while (removed) { - removed = false; - while (line.size() > 0 && line.get(line.size() - 1).glyphEntries.isEmpty()) { - line.remove(line.size() - 1); - removed = true; - } - if (line.size() > 0) { - SameStyleTextRecord lastRecord = line.get(line.size() - 1); - while (lastRecord.glyphEntries.size() > 0 - && Character.isWhitespace(lastRecord.glyphEntries.get(lastRecord.glyphEntries.size() - 1).character)) { - lastRecord.glyphEntries.remove(lastRecord.glyphEntries.size() - 1); - removed = true; - } - } - } - } - - textModel.calculateTextWidths(); - - List allTextRecords = new ArrayList<>(); - int lastHeight = 0; - int yOffset = -leading; - for (List line : lines) { - int width = 0; - int currentOffset = 0; - if (line.isEmpty()) { - currentOffset = lastHeight; - } else { - for (SameStyleTextRecord tr : line) { - width += tr.width; - int lineHeight = tr.style.fontHeight + tr.style.fontLeading; - lastHeight = lineHeight; - if (lineHeight > currentOffset) { - currentOffset = lineHeight; - } - } - } - yOffset += currentOffset; - int alignOffset = 0; - switch (align) { - case 0: // left - alignOffset = 0; - break; - case 1: // right - alignOffset = bounds.getWidth() - width; - break; - case 2: // center - alignOffset = (bounds.getWidth() - width) / 2; - break; - case 3: // justify - // todo; - break; - } - for (SameStyleTextRecord tr : line) { - tr.xOffset = alignOffset; - alignOffset += tr.width; - } - for (SameStyleTextRecord tr : line) { - TEXTRECORD tr2 = new TEXTRECORD(); - tr2.styleFlagsHasFont = fontId != 0; - tr2.fontId = fontId; - tr2.textHeight = tr.style.fontHeight; - if (tr.style.textColor != null) { - tr2.styleFlagsHasColor = true; - tr2.textColorA = tr.style.textColor; - } - // always add xOffset, because no xOffset and 0 xOffset is diffrent in text rendering - tr2.styleFlagsHasXOffset = true; - tr2.xOffset = tr.xOffset; - if (yOffset != 0) { - tr2.styleFlagsHasYOffset = true; - tr2.yOffset = yOffset; - } - tr2.glyphEntries = new ArrayList<>(tr.glyphEntries.size()); - for (GlyphCharacter ge : tr.glyphEntries) { - tr2.glyphEntries.add(ge.glyphEntry); - } - allTextRecords.add(tr2); - } - } - - switch (renderMode) { - case BITMAP: - staticTextToImage(swf, allTextRecords, 2, image, getTextMatrix(), transformation, colorTransform); - break; - case HTML5_CANVAS: - staticTextToHtmlCanvas(zoom, swf, allTextRecords, 2, htmlCanvasBuilder, getBounds(), getTextMatrix(), colorTransform); - break; - case SVG: - staticTextToSVG(swf, allTextRecords, 2, svgExporter, getBounds(), getTextMatrix(), colorTransform, zoom); - break; - } - } - } - - @Override - public ExportRectangle calculateTextBounds() { - return null; - } - - @Override - public int getNumFrames() { - return 1; - } - - @Override - public boolean isSingleFrame() { - return true; - } -} +/* + * Copyright (C) 2010-2015 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.flash.tags; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.SWFInputStream; +import com.jpexs.decompiler.flash.SWFOutputStream; +import com.jpexs.decompiler.flash.configuration.Configuration; +import com.jpexs.decompiler.flash.exporters.commonshape.ExportRectangle; +import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; +import com.jpexs.decompiler.flash.exporters.commonshape.SVGExporter; +import com.jpexs.decompiler.flash.helpers.HighlightedText; +import com.jpexs.decompiler.flash.helpers.HighlightedTextWriter; +import com.jpexs.decompiler.flash.helpers.hilight.HighlightSpecialType; +import com.jpexs.decompiler.flash.tags.base.BoundedTag; +import com.jpexs.decompiler.flash.tags.base.FontTag; +import com.jpexs.decompiler.flash.tags.base.MissingCharacterHandler; +import com.jpexs.decompiler.flash.tags.base.RenderContext; +import com.jpexs.decompiler.flash.tags.base.TextTag; +import com.jpexs.decompiler.flash.tags.dynamictext.CharacterWithStyle; +import com.jpexs.decompiler.flash.tags.dynamictext.DynamicTextModel; +import com.jpexs.decompiler.flash.tags.dynamictext.GlyphCharacter; +import com.jpexs.decompiler.flash.tags.dynamictext.Paragraph; +import com.jpexs.decompiler.flash.tags.dynamictext.SameStyleTextRecord; +import com.jpexs.decompiler.flash.tags.dynamictext.TextStyle; +import com.jpexs.decompiler.flash.tags.dynamictext.Word; +import com.jpexs.decompiler.flash.tags.enums.TextRenderMode; +import com.jpexs.decompiler.flash.tags.text.ParsedSymbol; +import com.jpexs.decompiler.flash.tags.text.TextAlign; +import com.jpexs.decompiler.flash.tags.text.TextLexer; +import com.jpexs.decompiler.flash.tags.text.TextParseException; +import com.jpexs.decompiler.flash.types.BasicType; +import com.jpexs.decompiler.flash.types.ColorTransform; +import com.jpexs.decompiler.flash.types.DynamicTextGlyphEntry; +import com.jpexs.decompiler.flash.types.MATRIX; +import com.jpexs.decompiler.flash.types.RECT; +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.types.annotations.Conditional; +import com.jpexs.decompiler.flash.types.annotations.SWFType; +import com.jpexs.helpers.ByteArrayRange; +import com.jpexs.helpers.SerializableImage; +import com.jpexs.helpers.utf8.Utf8Helper; +import java.awt.Color; +import java.awt.Font; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.Stack; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +/** + * + * @author JPEXS + */ +public class DefineEditTextTag extends TextTag { + + public static final int ID = 37; + + public static final String NAME = "DefineEditText"; + + @SWFType(BasicType.UI16) + public int characterID; + + public RECT bounds; + + public boolean hasText; + + public boolean wordWrap; + + public boolean multiline; + + public boolean password; + + public boolean readOnly; + + public boolean hasTextColor; + + public boolean hasMaxLength; + + public boolean hasFont; + + public boolean hasFontClass; + + public boolean autoSize; + + public boolean hasLayout; + + public boolean noSelect; + + public boolean border; + + public boolean wasStatic; + + public boolean html; + + public boolean useOutlines; + + @SWFType(BasicType.UI16) + @Conditional("hasFont") + public int fontId; + + @Conditional("hasFontClass") + public String fontClass; + + @SWFType(BasicType.UI16) + @Conditional("hasFont") + public int fontHeight; + + @Conditional("hasTextColor") + public RGBA textColor; + + @SWFType(BasicType.UI16) + @Conditional("hasMaxLength") + public int maxLength; + + @SWFType(BasicType.UI8) + @Conditional("hasLayout") + public int align; + + @SWFType(BasicType.UI16) + @Conditional("hasLayout") + public int leftMargin; + + @SWFType(BasicType.UI16) + @Conditional("hasLayout") + public int rightMargin; + + @SWFType(BasicType.UI16) + @Conditional("hasLayout") + public int indent; + + @SWFType(BasicType.SI16) + @Conditional("hasLayout") + public int leading; + + public String variableName; + + @Conditional("hasText") + public String initialText; + + /** + * Constructor + * + * @param swf + */ + public DefineEditTextTag(SWF swf) { + super(swf, ID, NAME, null); + characterID = swf.getNextCharacterId(); + bounds = new RECT(); + variableName = ""; + } + + /** + * Constructor + * + * @param sis + * @param data + * @throws IOException + */ + public DefineEditTextTag(SWFInputStream sis, ByteArrayRange data) throws IOException { + super(sis.getSwf(), ID, NAME, data); + readData(sis, data, 0, false, false, false); + } + + @Override + public final void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException { + characterID = sis.readUI16("characterID"); + bounds = sis.readRECT("bounds"); + hasText = sis.readUB(1, "hasText") == 1; + wordWrap = sis.readUB(1, "wordWrap") == 1; + multiline = sis.readUB(1, "multiline") == 1; + password = sis.readUB(1, "password") == 1; + readOnly = sis.readUB(1, "readOnly") == 1; + hasTextColor = sis.readUB(1, "hasTextColor") == 1; + hasMaxLength = sis.readUB(1, "hasMaxLength") == 1; + hasFont = sis.readUB(1, "hasFont") == 1; + hasFontClass = sis.readUB(1, "hasFontClass") == 1; + autoSize = sis.readUB(1, "autoSize") == 1; + hasLayout = sis.readUB(1, "hasLayout") == 1; + noSelect = sis.readUB(1, "noSelect") == 1; + border = sis.readUB(1, "border") == 1; + wasStatic = sis.readUB(1, "wasStatic") == 1; + html = sis.readUB(1, "html") == 1; + useOutlines = sis.readUB(1, "useOutlines") == 1; + if (hasFont) { + fontId = sis.readUI16("fontId"); + } + if (hasFontClass) { + fontClass = sis.readString("fontClass"); + } + if (hasFont) { + fontHeight = sis.readUI16("fontHeight"); + } + if (hasTextColor) { + textColor = sis.readRGBA("textColor"); + } + if (hasMaxLength) { + maxLength = sis.readUI16("maxLength"); + } + if (hasLayout) { + align = sis.readUI8("align"); //0 left, 1 right, 2 center, 3 justify + leftMargin = sis.readUI16("leftMargin"); + rightMargin = sis.readUI16("rightMargin"); + indent = sis.readUI16("indent"); + leading = sis.readSI16("leading"); + } + variableName = sis.readString("variableName"); + if (hasText) { + initialText = sis.readString("initialText"); + } + + } + + /** + * Gets data bytes + * + * @param sos SWF output stream + * @throws java.io.IOException + */ + @Override + public void getData(SWFOutputStream sos) throws IOException { + sos.writeUI16(characterID); + sos.writeRECT(bounds); + sos.writeUB(1, hasText ? 1 : 0); + sos.writeUB(1, wordWrap ? 1 : 0); + sos.writeUB(1, multiline ? 1 : 0); + sos.writeUB(1, password ? 1 : 0); + sos.writeUB(1, readOnly ? 1 : 0); + sos.writeUB(1, hasTextColor ? 1 : 0); + sos.writeUB(1, hasMaxLength ? 1 : 0); + sos.writeUB(1, hasFont ? 1 : 0); + sos.writeUB(1, hasFontClass ? 1 : 0); + sos.writeUB(1, autoSize ? 1 : 0); + sos.writeUB(1, hasLayout ? 1 : 0); + sos.writeUB(1, noSelect ? 1 : 0); + sos.writeUB(1, border ? 1 : 0); + sos.writeUB(1, wasStatic ? 1 : 0); + sos.writeUB(1, html ? 1 : 0); + sos.writeUB(1, useOutlines ? 1 : 0); + if (hasFont) { + sos.writeUI16(fontId); + } + if (hasFontClass) { + sos.writeString(fontClass); + } + if (hasFont) { + sos.writeUI16(fontHeight); + } + if (hasTextColor) { + sos.writeRGBA(textColor); + } + if (hasMaxLength) { + sos.writeUI16(maxLength); + } + if (hasLayout) { + sos.writeUI8(align); + sos.writeUI16(leftMargin); + sos.writeUI16(rightMargin); + sos.writeUI16(indent); + sos.writeSI16(leading); + } + sos.writeString(variableName); + if (hasText) { + sos.writeString(initialText); + } + } + + @Override + public RECT getBounds() { + return bounds; + } + + @Override + public MATRIX getTextMatrix() { + MATRIX matrix = new MATRIX(); + matrix.translateX = bounds.Xmin; + matrix.translateY = bounds.Ymin; + return matrix; + } + + @Override + public void setBounds(RECT r) { + bounds = r; + } + + private String stripTags(String inp) { + boolean intag = false; + String outp = ""; + inp = inp.replaceAll("
", "\r\n"); + for (int i = 0; i < inp.length(); ++i) { + if (!intag && inp.charAt(i) == '<') { + intag = true; + continue; + } + if (intag && inp.charAt(i) == '>') { + intag = false; + continue; + } + if (!intag) { + outp += inp.charAt(i); + } + } + return outp; + } + + private String entitiesReplace(String s) { + s = s.replace("<", "<"); + s = s.replace(">", ">"); + s = s.replace("&", "&"); + s = s.replace(""", "\""); + return s; + } + + @Override + public List getTexts() { + String ret = ""; + if (hasText) { + ret = initialText; + } + if (html) { + ret = stripTags(ret); + ret = entitiesReplace(ret); + } + return Arrays.asList(ret); + } + + private List getTextWithStyle() { + String str = ""; + TextStyle style = new TextStyle(); + style.font = swf.getFont(fontId); + style.fontHeight = fontHeight; + style.fontLeading = leading; + if (hasTextColor) { + style.textColor = textColor; + } + if (hasText) { + str = initialText; + } + final List ret = new ArrayList<>(); + if (html) { + SAXParserFactory factory = SAXParserFactory.newInstance(); + SAXParser saxParser; + final Stack styles = new Stack<>(); + styles.add(style); + try { + saxParser = factory.newSAXParser(); + DefaultHandler handler = new DefaultHandler() { + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + TextStyle style = styles.peek(); + switch (qName) { + case "p": + // todo: parse the following attribute: + // align + break; + case "b": + style = style.clone(); + style.bold = true; + styles.add(style); + break; + case "i": + style = style.clone(); + style.italic = true; + styles.add(style); + break; + case "u": + style = style.clone(); + style.underlined = true; + styles.add(style); + break; + case "font": + style = style.clone(); + String color = attributes.getValue("color"); + if (color != null) { + if (color.startsWith("#")) { + style.textColor = new RGBA(Color.decode(color)); + } + } + String size = attributes.getValue("size"); + if (size != null && size.length() > 0) { + char firstChar = size.charAt(0); + if (firstChar != '+' && firstChar != '-') { + int fontSize = Integer.parseInt(size); + style.fontHeight = (int) Math.round(fontSize * (style.font == null ? 1 : style.font.getDivider())); + style.fontLeading = leading; + } else { + // todo: parse relative sizes + } + } + String face = attributes.getValue("face"); + { + if (face != null && face.length() > 0) { + style.fontFace = face; + } + } + // todo: parse the following attributes: letterSpacing, kerning + styles.add(style); + break; + case "br": + case "sbr": // what's this? + CharacterWithStyle cs = new CharacterWithStyle(); + cs.character = '\n'; + cs.style = style; + ret.add(cs); + break; + } + //ret = entitiesReplace(ret); + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + switch (qName) { + case "b": + case "i": + case "u": + case "font": + styles.pop(); + break; + case "p": + TextStyle style = styles.peek(); + CharacterWithStyle cs = new CharacterWithStyle(); + cs.character = '\n'; + cs.style = style; + ret.add(cs); + break; + } + } + + @Override + public void characters(char[] ch, int start, int length) throws SAXException { + String txt = new String(ch, start, length); + TextStyle style = styles.peek(); + addCharacters(ret, txt, style); + } + }; + str = " \n" + + "]>" + str + ""; + saxParser.parse(new ByteArrayInputStream(str.getBytes(Utf8Helper.charset)), handler); + } catch (ParserConfigurationException | SAXException | IOException ex) { + Logger.getLogger(DefineEditTextTag.class.getName()).log(Level.SEVERE, null, ex); + } + } else { + addCharacters(ret, str, style); + } + return ret; + } + + private void addCharacters(List list, String str, TextStyle style) { + for (int i = 0; i < str.length(); i++) { + char ch = str.charAt(i); + CharacterWithStyle cs = new CharacterWithStyle(); + cs.character = ch; + cs.style = style; + list.add(cs); + } + } + + @Override + public List getFontIds() { + List ret = new ArrayList<>(); + ret.add(fontId); + return ret; + } + + @Override + public HighlightedText getFormattedText() { + HighlightedTextWriter writer = new HighlightedTextWriter(Configuration.getCodeFormatting(), true); + writer.append("["); + String[] alignNames = {"left", "right", "center", "justify"}; + String alignment; + if (align < alignNames.length) { + alignment = alignNames[align]; + } else { + alignment = "unknown"; + } + writer.newLine(); + writer.append("xmin " + bounds.Xmin).newLine(); + writer.append("ymin " + bounds.Ymin).newLine(); + writer.append("xmax " + bounds.Xmax).newLine(); + writer.append("ymax " + bounds.Ymax).newLine(); + if (wordWrap) { + writer.append("wordwrap 1").newLine(); + } + if (multiline) { + writer.append("multiline 1").newLine(); + } + if (password) { + writer.append("password 1").newLine(); + } + if (readOnly) { + writer.append("readonly 1").newLine(); + } + if (autoSize) { + writer.append("autosize 1").newLine(); + } + if (noSelect) { + writer.append("noselect 1").newLine(); + } + if (border) { + writer.append("border 1").newLine(); + } + if (wasStatic) { + writer.append("wasstatic 1").newLine(); + } + if (html) { + writer.append("html 1").newLine(); + } + if (useOutlines) { + writer.append("useoutlines 1").newLine(); + } + if (hasFont) { + writer.append("font " + fontId).newLine(); + writer.append("height " + fontHeight).newLine(); + } + if (hasTextColor) { + writer.append("color " + textColor.toHexARGB()).newLine(); + } + if (hasFontClass) { + writer.append("fontclass " + fontClass).newLine(); + } + if (hasMaxLength) { + writer.append("maxlength " + maxLength).newLine(); + } + writer.append("align " + alignment).newLine(); + if (hasLayout) { + writer.append("leftmargin " + leftMargin).newLine(); + writer.append("rightmargin " + rightMargin).newLine(); + writer.append("indent " + indent).newLine(); + writer.append("leading " + leading).newLine(); + } + if (!variableName.isEmpty()) { + writer.append("variablename " + variableName).newLine(); + } + writer.append("]"); + if (hasText) { + String text = initialText.replace("\\", "\\\\").replace("[", "\\[").replace("]", "\\]"); + writer.hilightSpecial(text, HighlightSpecialType.TEXT); + } + return new HighlightedText(writer); + } + + @Override + public boolean setFormattedText(MissingCharacterHandler missingCharHandler, String formattedText, String[] texts) throws TextParseException { + try { + TextLexer lexer = new TextLexer(new StringReader(formattedText)); + ParsedSymbol s = null; + formattedText = ""; + RECT bounds = new RECT(this.bounds); + boolean wordWrap = false; + boolean multiline = false; + boolean password = false; + boolean readOnly = false; + boolean autoSize = false; + boolean noSelect = false; + boolean border = false; + boolean wasStatic = false; + boolean html = false; + boolean useOutlines = false; + int fontId = -1; + int fontHeight = -1; + String fontClass = null; + RGBA textColor = null; + int maxLength = -1; + int align = -1; + int leftMargin = -1; + int rightMargin = -1; + int indent = -1; + int leading = -1; + String variableName = null; + + int textIdx = 0; + while ((s = lexer.yylex()) != null) { + switch (s.type) { + case PARAMETER: + String paramName = (String) s.values[0]; + String paramValue = (String) s.values[1]; + switch (paramName) { + case "xmin": + try { + bounds.Xmin = Integer.parseInt(paramValue); + } catch (NumberFormatException nfe) { + throw new TextParseException("Invalid xmin value. Number expected. Found: " + paramValue, lexer.yyline()); + } + break; + case "ymin": + try { + bounds.Ymin = Integer.parseInt(paramValue); + } catch (NumberFormatException nfe) { + throw new TextParseException("Invalid ymin value. Number expected. Found: " + paramValue, lexer.yyline()); + } + break; + case "xmax": + try { + bounds.Xmax = Integer.parseInt(paramValue); + } catch (NumberFormatException nfe) { + throw new TextParseException("Invalid xmax value. Number expected. Found: " + paramValue, lexer.yyline()); + } + break; + case "ymax": + try { + bounds.Ymax = Integer.parseInt(paramValue); + } catch (NumberFormatException nfe) { + throw new TextParseException("Invalid ymax value. Number expected. Found: " + paramValue, lexer.yyline()); + } + break; + case "wordwrap": + if (paramValue.equals("1")) { + wordWrap = true; + } + break; + case "multiline": + if (paramValue.equals("1")) { + multiline = true; + } + break; + case "password": + if (paramValue.equals("1")) { + password = true; + } + break; + case "readonly": + if (paramValue.equals("1")) { + readOnly = true; + } + break; + case "autosize": + if (paramValue.equals("1")) { + autoSize = true; + } + break; + case "noselect": + if (paramValue.equals("1")) { + noSelect = true; + } + break; + case "border": + if (paramValue.equals("1")) { + border = true; + } + break; + case "wasstatic": + if (paramValue.equals("1")) { + wasStatic = true; + } + break; + case "html": + if (paramValue.equals("1")) { + html = true; + } + break; + case "useoutlines": + if (paramValue.equals("1")) { + useOutlines = true; + } + break; + case "font": + try { + fontId = Integer.parseInt(paramValue); + + FontTag ft = swf.getFont(fontId); + if (ft == null) { + throw new TextParseException("Font not found.", lexer.yyline()); + } + } catch (NumberFormatException ne) { + throw new TextParseException("Invalid font value. Number expected. Found: " + paramValue, lexer.yyline()); + } + break; + case "fontclass": + fontClass = paramValue; + break; + case "height": + try { + fontHeight = Integer.parseInt(paramValue); + } catch (NumberFormatException ne) { + throw new TextParseException("Invalid height value. Number expected. Found: " + paramValue, lexer.yyline()); + } + break; + case "color": + Matcher m = Pattern.compile("#([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])").matcher(paramValue); + if (m.matches()) { + textColor = new RGBA(Integer.parseInt(m.group(2), 16), Integer.parseInt(m.group(3), 16), Integer.parseInt(m.group(4), 16), Integer.parseInt(m.group(1), 16)); + } else { + throw new TextParseException("Invalid color. Valid format is #aarrggbb. Found: " + paramValue, lexer.yyline()); + } + break; + case "maxlength": + try { + maxLength = Integer.parseInt(paramValue); + } catch (NumberFormatException ne) { + throw new TextParseException("Invalid maxLength value. Number expected. Found: " + paramValue, lexer.yyline()); + } + break; + case "align": + switch (paramValue) { + case "left": + align = 0; + break; + case "right": + align = 1; + break; + case "center": + align = 2; + break; + case "justify": + align = 3; + break; + default: + throw new TextParseException("Invalid align value. Expected one of: left,right,center or justify. Found: " + paramValue, lexer.yyline()); + } + break; + case "leftmargin": + try { + leftMargin = Integer.parseInt(paramValue); + } catch (NumberFormatException ne) { + throw new TextParseException("Invalid leftmargin value. Number expected. Found: " + paramValue, lexer.yyline()); + } + break; + case "rightmargin": + try { + rightMargin = Integer.parseInt(paramValue); + } catch (NumberFormatException ne) { + throw new TextParseException("Invalid rightmargin value. Number expected. Found: " + paramValue, lexer.yyline()); + } + break; + case "indent": + try { + indent = Integer.parseInt(paramValue); + } catch (NumberFormatException ne) { + throw new TextParseException("Invalid indent value. Number expected. Found: " + paramValue, lexer.yyline()); + } + break; + case "leading": + try { + leading = Integer.parseInt(paramValue); + } catch (NumberFormatException ne) { + throw new TextParseException("Invalid leading value. Number expected. Found: " + paramValue, lexer.yyline()); + } + break; + case "variablename": + variableName = paramValue; + break; + default: + throw new TextParseException("Unrecognized parameter name: " + paramName, lexer.yyline()); + } + break; + case TEXT: + String s2 = (String) s.values[0]; + if (s2 == null) { + s2 = ""; + } + + formattedText += (texts == null || textIdx >= texts.length) ? s2 : texts[textIdx++]; + formattedText = formattedText.replace("\r\n", "\r"); + break; + } + } + + setModified(true); + this.bounds = bounds; + if (formattedText.length() > 0) { + initialText = formattedText; + this.hasText = true; + } else { + this.hasText = false; + } + this.wordWrap = wordWrap; + this.multiline = multiline; + this.password = password; + this.readOnly = readOnly; + this.noSelect = noSelect; + this.border = border; + this.wasStatic = wasStatic; + this.html = html; + this.useOutlines = useOutlines; + if (textColor != null) { + hasTextColor = true; + this.textColor = textColor; + } + if (maxLength > -1) { + this.maxLength = maxLength; + hasMaxLength = true; + } + if (fontId > -1) { + this.fontId = fontId; + } + if (fontHeight > -1) { + this.fontHeight = fontHeight; + } + if (fontClass != null) { + this.fontClass = fontClass; + hasFontClass = true; + } + this.autoSize = autoSize; + this.align = align; + if ((leftMargin > -1) + || (rightMargin > -1) + || (indent > -1) + || (leading > -1)) { + this.leftMargin = leftMargin; + this.rightMargin = rightMargin; + this.indent = indent; + this.leading = leading; + hasLayout = true; + } + if (variableName == null) { + variableName = ""; + } + this.variableName = variableName; + + } catch (IOException ex) { + Logger.getLogger(DefineEditTextTag.class.getName()).log(Level.SEVERE, null, ex); + return false; + } + + return true; + } + + @Override + public void updateTextBounds() { + } + + @Override + public boolean alignText(TextAlign textAlign) { + return true; + } + + @Override + public boolean translateText(int diff) { + return true; + } + + @Override + public RECT getRect(Set added) { + return bounds; + } + + @Override + public int getCharacterId() { + return characterID; + } + + @Override + public void setCharacterId(int characterId) { + this.characterID = characterId; + } + + @Override + public void getNeededCharacters(Set needed) { + if (hasFont) { + needed.add(fontId); + } + } + + @Override + public boolean replaceCharacter(int oldCharacterId, int newCharacterId) { + if (fontId == oldCharacterId) { + fontId = newCharacterId; + setModified(true); + return true; + } + return false; + } + + @Override + public boolean removeCharacter(int characterId) { + if (fontId == characterId) { + hasFont = false; + fontId = 0; + setModified(true); + return true; + } + return false; + } + + @Override + public int getUsedParameters() { + return 0; + } + + @Override + public void toImage(int frame, int time, int ratio, RenderContext renderContext, SerializableImage image, Matrix transformation, ColorTransform colorTransform) { + render(TextRenderMode.BITMAP, image, null, null, transformation, colorTransform, 1); + } + + @Override + public void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level, double zoom) { + render(TextRenderMode.SVG, null, exporter, null, new Matrix(), colorTransform, zoom); + } + + @Override + public void toHtmlCanvas(StringBuilder result, double unitDivisor) { + render(TextRenderMode.HTML5_CANVAS, null, null, result, new Matrix(), new ColorTransform(), unitDivisor); + } + + private void render(TextRenderMode renderMode, SerializableImage image, SVGExporter svgExporter, StringBuilder htmlCanvasBuilder, Matrix transformation, ColorTransform colorTransform, double zoom) { + if (border) { + // border is always black, fill color is always white? + RGB borderColor = new RGBA(Color.black); + RGB fillColor = new RGBA(Color.white); + switch (renderMode) { + case BITMAP: + drawBorder(swf, image, borderColor, fillColor, getRect(), getTextMatrix(), transformation, colorTransform); + break; + case HTML5_CANVAS: + drawBorderHtmlCanvas(swf, htmlCanvasBuilder, borderColor, fillColor, getRect(), getTextMatrix(), colorTransform, zoom); + break; + case SVG: + drawBorderSVG(swf, svgExporter, borderColor, fillColor, getRect(), getTextMatrix(), colorTransform, zoom); + break; + } + } + if (hasText) { + DynamicTextModel textModel = new DynamicTextModel(); + List txt = getTextWithStyle(); + TextStyle lastStyle = null; + char prevChar = 0; + boolean lastWasWhiteSpace = false; + for (int i = 0; i < txt.size(); i++) { + CharacterWithStyle cs = txt.get(i); + char c = cs.character; + if (c != '\r' && c != '\n') { + // create new SameStyleTextRecord for all words and all diffrent style text parts + if (lastWasWhiteSpace && !Character.isWhitespace(c)) { + textModel.newWord(); + lastWasWhiteSpace = false; + } + if (cs.style != lastStyle) { + lastStyle = cs.style; + textModel.style = lastStyle; + textModel.newRecord(); + } + Character nextChar = null; + if (i + 1 < txt.size()) { + nextChar = txt.get(i + 1).character; + } + int advance; + FontTag font = lastStyle.font; + DynamicTextGlyphEntry ge = new DynamicTextGlyphEntry(); + ge.fontFace = lastStyle.fontFace; + ge.fontStyle = (lastStyle.bold ? Font.BOLD : 0) | (lastStyle.italic ? Font.ITALIC : 0); + ge.character = c; + ge.glyphIndex = font == null ? -1 : font.charToGlyph(c); + if (font != null && font.hasLayout()) { + int kerningAdjustment = 0; + if (nextChar != null) { + kerningAdjustment = font.getCharKerningAdjustment(c, nextChar); + kerningAdjustment /= font.getDivider(); + } + advance = (int) Math.round(Math.round((double) lastStyle.fontHeight * (font.getGlyphAdvance(ge.glyphIndex) + kerningAdjustment) / (font.getDivider() * 1024.0))); + } else { + String fontName = lastStyle.fontFace != null ? lastStyle.fontFace : FontTag.defaultFontName; + int fontStyle = font == null ? ge.fontStyle : font.getFontStyle(); + advance = (int) Math.round(SWF.unitDivisor * FontTag.getSystemFontAdvance(fontName, fontStyle, (int) (lastStyle.fontHeight / SWF.unitDivisor), c, nextChar)); + } + ge.glyphAdvance = advance; + textModel.addGlyph(c, ge); + if (Character.isWhitespace(c)) { + lastWasWhiteSpace = true; + } + } else { + if (multiline) { + textModel.newParagraph(); + } + } + prevChar = c; + } + + textModel.calculateTextWidths(); + List> lines; + if (multiline && wordWrap) { + lines = new ArrayList<>(); + for (Paragraph paragraph : textModel.paragraphs) { + List line = new ArrayList<>(); + int lineLength = 0; + for (Word word : paragraph.words) { + if (lineLength + word.width <= bounds.getWidth()) { + line.addAll(word.records); + lineLength += word.width; + } else { + lines.add(line); + line = new ArrayList<>(); + line.addAll(word.records); + lineLength = 0; + } + } + if (!line.isEmpty()) { + lines.add(line); + } + } + } else { + lines = new ArrayList<>(); + for (Paragraph paragraph : textModel.paragraphs) { + List line = new ArrayList<>(); + for (Word word : paragraph.words) { + for (SameStyleTextRecord tr : word.records) { + line.add(tr); + } + } + lines.add(line); + } + } + + // remove spaces after last word + for (List line : lines) { + boolean removed = true; + while (removed) { + removed = false; + while (line.size() > 0 && line.get(line.size() - 1).glyphEntries.isEmpty()) { + line.remove(line.size() - 1); + removed = true; + } + if (line.size() > 0) { + SameStyleTextRecord lastRecord = line.get(line.size() - 1); + while (lastRecord.glyphEntries.size() > 0 + && Character.isWhitespace(lastRecord.glyphEntries.get(lastRecord.glyphEntries.size() - 1).character)) { + lastRecord.glyphEntries.remove(lastRecord.glyphEntries.size() - 1); + removed = true; + } + } + } + } + + textModel.calculateTextWidths(); + + List allTextRecords = new ArrayList<>(); + int lastHeight = 0; + int yOffset = -leading; + for (List line : lines) { + int width = 0; + int currentOffset = 0; + if (line.isEmpty()) { + currentOffset = lastHeight; + } else { + for (SameStyleTextRecord tr : line) { + width += tr.width; + int lineHeight = tr.style.fontHeight + tr.style.fontLeading; + lastHeight = lineHeight; + if (lineHeight > currentOffset) { + currentOffset = lineHeight; + } + } + } + yOffset += currentOffset; + int alignOffset = 0; + switch (align) { + case 0: // left + alignOffset = 0; + break; + case 1: // right + alignOffset = bounds.getWidth() - width; + break; + case 2: // center + alignOffset = (bounds.getWidth() - width) / 2; + break; + case 3: // justify + // todo; + break; + } + for (SameStyleTextRecord tr : line) { + tr.xOffset = alignOffset; + alignOffset += tr.width; + } + for (SameStyleTextRecord tr : line) { + TEXTRECORD tr2 = new TEXTRECORD(); + tr2.styleFlagsHasFont = fontId != 0; + tr2.fontId = fontId; + tr2.textHeight = tr.style.fontHeight; + if (tr.style.textColor != null) { + tr2.styleFlagsHasColor = true; + tr2.textColorA = tr.style.textColor; + } + // always add xOffset, because no xOffset and 0 xOffset is diffrent in text rendering + tr2.styleFlagsHasXOffset = true; + tr2.xOffset = tr.xOffset; + if (yOffset != 0) { + tr2.styleFlagsHasYOffset = true; + tr2.yOffset = yOffset; + } + tr2.glyphEntries = new ArrayList<>(tr.glyphEntries.size()); + for (GlyphCharacter ge : tr.glyphEntries) { + tr2.glyphEntries.add(ge.glyphEntry); + } + allTextRecords.add(tr2); + } + } + + switch (renderMode) { + case BITMAP: + staticTextToImage(swf, allTextRecords, 2, image, getTextMatrix(), transformation, colorTransform); + break; + case HTML5_CANVAS: + staticTextToHtmlCanvas(zoom, swf, allTextRecords, 2, htmlCanvasBuilder, getBounds(), getTextMatrix(), colorTransform); + break; + case SVG: + staticTextToSVG(swf, allTextRecords, 2, svgExporter, getBounds(), getTextMatrix(), colorTransform, zoom); + break; + } + } + } + + @Override + public ExportRectangle calculateTextBounds() { + return null; + } + + @Override + public int getNumFrames() { + return 1; + } + + @Override + public boolean isSingleFrame() { + return true; + } +} diff --git a/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java b/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java index e9ad67bee..ecca43947 100644 --- a/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java +++ b/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java @@ -1,1937 +1,1937 @@ -/* - * Copyright (C) 2010-2015 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.console; - -import com.jpexs.decompiler.flash.AbortRetryIgnoreHandler; -import com.jpexs.decompiler.flash.ApplicationInfo; -import com.jpexs.decompiler.flash.EventListener; -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.SWFBundle; -import com.jpexs.decompiler.flash.SWFCompression; -import com.jpexs.decompiler.flash.SWFSourceInfo; -import com.jpexs.decompiler.flash.SearchMode; -import com.jpexs.decompiler.flash.SwfOpenException; -import com.jpexs.decompiler.flash.abc.ABC; -import com.jpexs.decompiler.flash.abc.RenameType; -import com.jpexs.decompiler.flash.abc.ScriptPack; -import com.jpexs.decompiler.flash.abc.avm2.AVM2Code; -import com.jpexs.decompiler.flash.abc.avm2.parser.AVM2ParseException; -import com.jpexs.decompiler.flash.abc.avm2.parser.pcode.ASM3Parser; -import com.jpexs.decompiler.flash.abc.avm2.parser.pcode.MissingSymbolHandler; -import com.jpexs.decompiler.flash.abc.avm2.parser.script.ActionScript3Parser; -import com.jpexs.decompiler.flash.abc.types.MethodBody; -import com.jpexs.decompiler.flash.abc.types.traits.Trait; -import com.jpexs.decompiler.flash.action.parser.ActionParseException; -import com.jpexs.decompiler.flash.action.parser.pcode.ASMParser; -import com.jpexs.decompiler.flash.action.parser.script.ActionScript2Parser; -import com.jpexs.decompiler.flash.configuration.Configuration; -import com.jpexs.decompiler.flash.configuration.ConfigurationItem; -import com.jpexs.decompiler.flash.exporters.BinaryDataExporter; -import com.jpexs.decompiler.flash.exporters.FontExporter; -import com.jpexs.decompiler.flash.exporters.FrameExporter; -import com.jpexs.decompiler.flash.exporters.ImageExporter; -import com.jpexs.decompiler.flash.exporters.MorphShapeExporter; -import com.jpexs.decompiler.flash.exporters.MovieExporter; -import com.jpexs.decompiler.flash.exporters.ShapeExporter; -import com.jpexs.decompiler.flash.exporters.SoundExporter; -import com.jpexs.decompiler.flash.exporters.TextExporter; -import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; -import com.jpexs.decompiler.flash.exporters.modes.BinaryDataExportMode; -import com.jpexs.decompiler.flash.exporters.modes.ButtonExportMode; -import com.jpexs.decompiler.flash.exporters.modes.FontExportMode; -import com.jpexs.decompiler.flash.exporters.modes.FrameExportMode; -import com.jpexs.decompiler.flash.exporters.modes.ImageExportMode; -import com.jpexs.decompiler.flash.exporters.modes.MorphShapeExportMode; -import com.jpexs.decompiler.flash.exporters.modes.MovieExportMode; -import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; -import com.jpexs.decompiler.flash.exporters.modes.ShapeExportMode; -import com.jpexs.decompiler.flash.exporters.modes.SoundExportMode; -import com.jpexs.decompiler.flash.exporters.modes.SpriteExportMode; -import com.jpexs.decompiler.flash.exporters.modes.TextExportMode; -import com.jpexs.decompiler.flash.exporters.settings.BinaryDataExportSettings; -import com.jpexs.decompiler.flash.exporters.settings.ButtonExportSettings; -import com.jpexs.decompiler.flash.exporters.settings.FontExportSettings; -import com.jpexs.decompiler.flash.exporters.settings.FrameExportSettings; -import com.jpexs.decompiler.flash.exporters.settings.ImageExportSettings; -import com.jpexs.decompiler.flash.exporters.settings.MorphShapeExportSettings; -import com.jpexs.decompiler.flash.exporters.settings.MovieExportSettings; -import com.jpexs.decompiler.flash.exporters.settings.ScriptExportSettings; -import com.jpexs.decompiler.flash.exporters.settings.ShapeExportSettings; -import com.jpexs.decompiler.flash.exporters.settings.SoundExportSettings; -import com.jpexs.decompiler.flash.exporters.settings.SpriteExportSettings; -import com.jpexs.decompiler.flash.exporters.settings.TextExportSettings; -import com.jpexs.decompiler.flash.exporters.swf.SwfXmlExporter; -import com.jpexs.decompiler.flash.gui.Main; -import com.jpexs.decompiler.flash.gui.helpers.CheckResources; -import com.jpexs.decompiler.flash.helpers.FileTextWriter; -import com.jpexs.decompiler.flash.importers.BinaryDataImporter; -import com.jpexs.decompiler.flash.importers.ImageImporter; -import com.jpexs.decompiler.flash.importers.ShapeImporter; -import com.jpexs.decompiler.flash.importers.SwfXmlImporter; -import com.jpexs.decompiler.flash.tags.DefineBinaryDataTag; -import com.jpexs.decompiler.flash.tags.DefineBitsJPEG2Tag; -import com.jpexs.decompiler.flash.tags.DefineBitsJPEG3Tag; -import com.jpexs.decompiler.flash.tags.DefineSpriteTag; -import com.jpexs.decompiler.flash.tags.JPEGTablesTag; -import com.jpexs.decompiler.flash.tags.Tag; -import com.jpexs.decompiler.flash.tags.base.ASMSource; -import com.jpexs.decompiler.flash.tags.base.ButtonTag; -import com.jpexs.decompiler.flash.tags.base.CharacterIdTag; -import com.jpexs.decompiler.flash.tags.base.CharacterTag; -import com.jpexs.decompiler.flash.tags.base.ImageTag; -import com.jpexs.decompiler.flash.tags.base.ShapeTag; -import com.jpexs.decompiler.flash.tags.base.SoundTag; -import com.jpexs.decompiler.flash.treeitems.SWFList; -import com.jpexs.decompiler.flash.types.ColorTransform; -import com.jpexs.decompiler.flash.types.RECT; -import com.jpexs.decompiler.flash.types.sound.SoundFormat; -import com.jpexs.decompiler.flash.xfl.FLAVersion; -import com.jpexs.decompiler.graph.CompilationException; -import com.jpexs.helpers.Helper; -import com.jpexs.helpers.Path; -import com.jpexs.helpers.streams.SeekableInputStream; -import com.sun.jna.Platform; -import com.sun.jna.platform.win32.Kernel32; -import gnu.jpdf.PDFJob; -import java.awt.Color; -import java.awt.Graphics; -import java.awt.image.BufferedImage; -import java.awt.print.PageFormat; -import java.awt.print.Paper; -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.FilenameFilter; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.PrintStream; -import java.io.StringReader; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Stack; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * - * @author JPEXS - */ -public class CommandLineArgumentParser { - - private static final Logger logger = Logger.getLogger(CommandLineArgumentParser.class.getName()); - - private static boolean commandLineMode = false; - - private static String stdOut = null; - - private static String stdErr = null; - - @SuppressWarnings("unchecked") - private static final ConfigurationItem[] commandlineConfigBoolean = new ConfigurationItem[]{ - Configuration.decompile, - Configuration.parallelSpeedUp, - Configuration.internalFlashViewer, - //Configuration.autoDeobfuscate, - Configuration.cacheOnDisk - }; - - public static boolean isCommandLineMode() { - return commandLineMode; - } - - public static void printCmdLineUsage() { - printCmdLineUsage(System.out, true); - } - - public static void printCmdLineUsage(PrintStream out, boolean printConfigs) { - int cnt = 1; - out.println("Commandline arguments:"); - out.println(" " + (cnt++) + ") -help | --help | /?"); - out.println(" ...shows commandline arguments (this help)"); - out.println(" " + (cnt++) + ") [ ...]"); - out.println(" ...opens SWF file(s) with the decompiler GUI"); - out.println(" " + (cnt++) + ") -proxy [-P]"); - out.println(" ...auto start proxy in the tray. Optional parameter -P specifies port for proxy. Defaults to 55555. "); - out.println(" " + (cnt++) + ") -export [-selectas3class ...]"); - out.println(" ...export sources to ."); - out.println(" Exports all files from when it is a folder."); - out.println(" Values for parameter:"); - out.println(" script - Scripts (Default format: ActionScript source)"); - out.println(" - Optional DEPRECATED \"-selectas3class\" parameter can be passed in same way as -selectclass"); - out.println(" image - Images (Default format: PNG/JPEG)"); - out.println(" shape - Shapes (Default format: SVG)"); - out.println(" morphshape - MorphShapes (Default format: SVG)"); - out.println(" movie - Movies (Default format: FLV without sound)"); - out.println(" font - Fonts (Default format: TTF)"); - out.println(" frame - Frames (Default format: PNG)"); - out.println(" sprite - Sprites (Default format: PNG)"); - out.println(" button - Buttons (Default format: PNG)"); - out.println(" sound - Sounds (Default format: MP3/WAV/FLV only sound)"); - out.println(" binaryData - Binary data (Default format: Raw data)"); - out.println(" text - Texts (Default format: Plain text)"); - out.println(" all - Every resource (but not FLA and XFL)"); - out.println(" fla - Everything to FLA compressed format"); - out.println(" xfl - Everything to uncompressed FLA format (XFL)"); - out.println(" You can export multiple types of items by using colon \",\""); - out.println(" DO NOT PUT space between comma (,) and next value."); - out.println(); - out.println(" Old DEPRECATED aliases include: (please use basic itemtypes and -format parameter instead)"); - out.println(" as, pcode, pcodehex, hex, all_as, all_pcode, all_pcodehex, textplain"); - out.println(); - out.println(" " + (cnt++) + ") -format "); - out.println(" ...sets output formats for export"); - out.println(" Values for parameter:"); - out.println(" script:as - ActionScript source"); - out.println(" script:pcode - ActionScript P-code"); - out.println(" script:pcodehex - ActionScript P-code with hex"); - out.println(" script:hex - ActionScript Hex only"); - out.println(" shape:svg - SVG format for Shapes"); - out.println(" shape:png - PNG format for Shapes"); - out.println(" shape:canvas - HTML5 Canvas format for Shapes"); - out.println(" shape:bmp - BMP format for Shapes"); - out.println(" morphshape:svg - SVG format for MorphShapes"); - out.println(" morphshape:canvas - HTML5 Canvas format for MorphShapes"); - out.println(" frame:png - PNG format for Frames"); - out.println(" frame:gif - GIF format for Frames"); - out.println(" frame:avi - AVI format for Frames"); - out.println(" frame:svg - SVG format for Frames"); - out.println(" frame:canvas - HTML5 Canvas format for Frames"); - out.println(" frame:pdf - PDF format for Frames"); - out.println(" frame:bmp - BMP format for Frames"); - out.println(" sprite:png - PNG format for Sprites"); - out.println(" sprite:gif - GIF format for Sprites"); - out.println(" sprite:avi - AVI format for Sprites"); - out.println(" sprite:svg - SVG format for Sprites"); - out.println(" sprite:canvas - HTML5 Canvas format for Sprites"); - out.println(" sprite:pdf - PDF format for Sprites"); - out.println(" sprite:bmp - BMP format for Sprites"); - out.println(" button:png - PNG format for Buttons"); - out.println(" button:svg - SVG format for Buttons"); - out.println(" button:bmp - BMP format for Buttons"); - out.println(" image:png_gif_jpeg - PNG/GIF/JPEG format for Images"); - out.println(" image:png - PNG format for Images"); - out.println(" image:jpeg - JPEG format for Images"); - out.println(" image:bmp - BMP format for Images"); - out.println(" text:plain - Plain text format for Texts"); - out.println(" text:formatted - Formatted text format for Texts"); - out.println(" text:svg - SVG format for Texts"); - out.println(" sound:mp3_wav_flv - MP3/WAV/FLV format for Sounds"); - out.println(" sound:mp3_wav - MP3/WAV format for Sounds"); - out.println(" sound:wav - WAV format for Sounds"); - out.println(" sound:flv - FLV format for Sounds"); - out.println(" font:ttf - TTF format for Fonts"); - out.println(" font:woff - WOFF format for Fonts"); - out.println(" fla: or xfl: - Specify FLA format version"); - out.println(" - values for : cs5,cs5.5,cs6,cc"); - - out.println(" You can set multiple formats at once using comma (,)"); - out.println(" DO NOT PUT space between comma (,) and next value."); - out.println(" The prefix with colon (:) is neccessary."); - out.println(" " + (cnt++) + ") -select "); - out.println(" ...selects frames/pages for export"); - out.println(" Example formats:"); - out.println(" 1-5"); - out.println(" 2,3"); - out.println(" 2-5,7,9-"); - out.println(" DO NOT PUT space between comma (,) and next ramge."); - out.println(" " + (cnt++) + ") -selectid "); - out.println(" ...selects characters for export by character id"); - out.println(" format is same as in -select"); - out.println(" " + (cnt++) + ") -selectclass "); - out.println(" ...selects scripts to export by class name (ActionScript 3 ONLY)"); - out.println(" format:"); - out.println(" com.example.MyClass"); - out.println(" com.example.+ (all classes in package \"com.example\")"); - out.println(" com.++,net.company.MyClass (all classes in package \"com\" and all subpackages, class net.company.MyClass)"); - out.println(" DO NOT PUT space between comma (,) and next class."); - out.println(" " + (cnt++) + ") -dumpSWF "); - out.println(" ...dumps list of SWF tags to console"); - out.println(" " + (cnt++) + ") -dumpAS2 "); - out.println(" ...dumps list of AS1/2 sctipts to console"); - out.println(" " + (cnt++) + ") -dumpAS3 "); - out.println(" ...dumps list of AS3 sctipts to console"); - out.println(" " + (cnt++) + ") -compress [(zlib|lzma)]"); - out.println(" ...Compress SWF and save it to . If is already compressed, it will be re-compressed. Default compression method is ZLIB"); - out.println(" " + (cnt++) + ") -decompress "); - out.println(" ...Decompress and save it to "); - out.println(" " + (cnt++) + ") -swf2xml "); - out.println(" ...Converts the SWF to XML file"); - out.println(" " + (cnt++) + ") -xml2swf "); - out.println(" ...Converts the XML to SWF file"); - out.println(" " + (cnt++) + ") -extract [-o |] [nocheck] [(all|biggest|smallest|first|last)]"); - out.println(" ...Extracts SWF files from ZIP or other binary files"); - out.println(" ...-o parameter should contain a file path when \"biggest\" or \"first\" parameter is specified"); - out.println(" ...-o parameter should contain a folder path when no exctaction mode or \"all\" parameter is specified"); - out.println(" " + (cnt++) + ") -renameInvalidIdentifiers (typeNumber|randomWord) "); - out.println(" ...Renames the invalid identifiers in and save it to "); - out.println(" " + (cnt++) + ") -config key=value[,key2=value2][,key3=value3...] [other parameters]"); - out.print(" ...Sets configuration values. "); - if (printConfigs) { - out.print("Available keys[current setting]:"); - for (ConfigurationItem item : commandlineConfigBoolean) { - out.print(" " + item + "[" + item.get() + "]"); - } - } - out.println(""); - out.println(" Values are boolean, you can use 0/1, true/false, on/off or yes/no."); - out.println(" If no other parameters passed, configuration is saved. Otherwise it is used only once."); - out.println(" DO NOT PUT space between comma (,) and next value."); - out.println(" " + (cnt++) + ") -onerror (abort|retryN|ignore)"); - out.println(" ...error handling mode. \"abort\" stops the exporting, \"retry\" tries the exporting N times, \"ignore\" ignores the current file"); - out.println(" " + (cnt++) + ") -timeout "); - out.println(" ...decompilation timeout for a single method in AS3 or single action in AS1/2 in seconds"); - out.println(" " + (cnt++) + ") -exportTimeout "); - out.println(" ...total export timeout in seconds"); - out.println(" " + (cnt++) + ") -exportFileTimeout "); - out.println(" ...export timeout for a single AS3 class in seconds"); - out.println(" " + (cnt++) + ") -flashpaper2pdf "); - out.println(" ...converts FlashPaper SWF file to PDF . Use -zoom parameter to specify image quality."); - out.println(" " + (cnt++) + ") -zoom "); - out.println(" ...apply zoom during export (currently for FlashPaper conversion only)"); - out.println(" " + (cnt++) + ") -replace (|) [methodBodyIndex1] [(|) [methodBodyIndex2]]..."); - out.println(" ...replaces the data of the specified BinaryData, Image, DefineSound tag or Script"); - out.println(" ...methodBodyIndexN parameter should be specified if and only if the imported entity is an AS3 P-Code"); - printCmdLineUsageExamples(out); - } - - private static void printCmdLineUsageExamples(PrintStream out) { - out.println(); - out.println("Examples:"); - out.println("java -jar ffdec.jar myfile.swf"); - out.println("java -jar ffdec.jar -proxy"); - out.println("java -jar ffdec.jar -proxy -P1234"); - out.println("java -jar ffdec.jar -export script \"C:\\decompiled\" myfile.swf"); - out.println("java -jar ffdec.jar -selectclass com.example.MyClass,com.example.SecondClass -export script \"C:\\decompiled\" myfile.swf"); - out.println("java -jar ffdec.jar -format script:pcode -export script \"C:\\decompiled\" myfile.swf"); - out.println("java -jar ffdec.jar -format script:pcode,text:plain -export script,text,image \"C:\\decompiled\" myfile.swf"); - out.println("java -jar ffdec.jar -format fla:cs5.5 -export fla \"C:\\sources\\myfile.fla\" myfile.swf"); - out.println("java -jar ffdec.jar -dumpSWF myfile.swf"); - out.println("java -jar ffdec.jar -compress myfile.swf myfiledec.swf"); - out.println("java -jar ffdec.jar -decompress myfiledec.swf myfile.swf"); - out.println("java -jar ffdec.jar -onerror ignore -export script \"C:\\decompiled\" myfile.swf"); - out.println("java -jar ffdec.jar -onerror retry 5 -export script \"C:\\decompiled\" myfile.swf"); - out.println("java -jar ffdec.jar -config autoDeobfuscate=1,parallelSpeedUp=0 -export script \"C:\\decompiled\" myfile.swf"); - out.println(""); - out.println("Instead of \"java -jar ffdec.jar\" you can use ffdec.bat on Windows, ffdec.sh on Linux/MacOs"); - } - - /** - * Parses the console arguments - * - * @param arguments Arguments - * @return paths to the file which should be opened or null - * @throws java.io.IOException On error - */ - public static String[] parseArguments(String[] arguments) throws IOException { - Level traceLevel = Level.WARNING; - Stack args = new Stack<>(); - for (int i = arguments.length - 1; i >= 0; i--) { - String arg = arguments[i]; - if (arg.length() > 0) { - args.add(arg); - } - } - - AbortRetryIgnoreHandler handler = null; - Map format = new HashMap<>(); - double zoom = 1; - Selection selection = new Selection(); - Selection selectionIds = new Selection(); - List selectionClasses = null; - String nextParam = null, nextParamOriginal = null; - OUTER: - while (true) { - nextParamOriginal = args.pop(); - if (nextParamOriginal != null) { - nextParam = nextParamOriginal.toLowerCase(); - } - switch (nextParam) { - case "-selectid": - selectionIds = parseSelect(args); - break; - case "-select": - selection = parseSelect(args); - break; - case "-selectclass": - selectionClasses = parseSelectClass(args); - break; - case "-zoom": - zoom = parseZoom(args); - break; - case "-format": - format = parseFormat(args); - break; - case "-config": - parseConfig(args); - if (args.isEmpty()) { - Configuration.saveConfig(); - System.out.println("Configuration saved"); - return null; - } - break; - case "-onerror": - handler = parseOnError(args); - break; - case "-timeout": - parseTimeout(args); - break; - case "-exporttimeout": - parseExportTimeout(args); - break; - case "-exportfiletimeout": - parseExportFileTimeout(args); - break; - case "-stdout": - parseStdOut(args); - break; - case "-stderr": - parseStdErr(args); - break; - case "-affinity": - parseAffinity(args); - break; - case "-priority": - parsePriority(args); - break; - case "-verbose": - traceLevel = Level.FINE; - break; - case "-debug": - for (int i = 0; i < arguments.length; i++) { - System.out.println(i + ".:" + arguments[i]); - } - Configuration.debugMode.set(true); - break; - case "--debugtool": - parseDebugTool(args); - break; - case "--compareresources": - parseCompareResources(args); - break; - default: - break OUTER; - } - if (args.isEmpty()) { - return null; - } - } - - String command = ""; - if (nextParam.startsWith("-")) { - command = nextParam.substring(1); - } - - if (command.equals("removefromcontextmenu")) { - if (!args.isEmpty()) { - badArguments(command); - } - ContextMenuTools.addToContextMenu(false, true); - System.exit(0); - } else if (command.equals("addtocontextmenu")) { - if (!args.isEmpty()) { - badArguments(command); - } - ContextMenuTools.addToContextMenu(true, true); - System.exit(0); - } else if (command.equals("proxy")) { - parseProxy(args); - } else if (command.equals("export")) { - parseExport(selectionClasses, selection, selectionIds, args, handler, traceLevel, format, zoom); - } else if (command.equals("compress")) { - parseCompress(args); - } else if (command.equals("decompress")) { - parseDecompress(args); - } else if (command.equals("swf2xml")) { - parseSwf2Xml(args); - } else if (command.equals("xml2swf")) { - parseXml2Swf(args); - } else if (command.equals("extract")) { - parseExtract(args); - } else if (command.equals("renameinvalididentifiers")) { - parseRenameInvalidIdentifiers(args); - } else if (command.equals("dumpswf")) { - parseDumpSwf(args); - } else if (command.equals("dumpas2")) { - parseDumpAS2(args); - } else if (command.equals("dumpas3")) { - parseDumpAS3(args); - } else if (command.equals("flashpaper2pdf")) { - parseFlashPaperToPdf(selection, zoom, args); - } else if (command.equals("replace")) { - parseReplace(args); - } else if (command.equals("as3compiler")) { - ActionScript3Parser.compile(null /*?*/, args.pop(), args.pop(), 0); - } else if (nextParam.equals("-help") || nextParam.equals("--help") || nextParam.equals("/?") || nextParam.equals("\\_") /* /? translates as this on windows */) { - printHeader(); - printCmdLineUsage(); - System.exit(0); - } else if (nextParam.equals("--webhelp")) { //for generating commandline usage on webpages - ByteArrayOutputStream whbaos = new ByteArrayOutputStream(); - printCmdLineUsage(new PrintStream(whbaos, true), false); - String wh = new String(whbaos.toByteArray()); - wh = wh.replace("<", "<").replace(">", ">"); - System.out.println(wh); - } else { - args.push(nextParamOriginal); // file names should be the original one - String[] fileNames = args.toArray(new String[args.size()]); - boolean allParamIsAFile = true; - for (String fileName : fileNames) { - File file = new File(fileName); - if (!file.exists() || !file.isFile()) { - allParamIsAFile = false; - } - } - - if (allParamIsAFile) { - return fileNames; - } else { - badArguments(); - } - } - - return null; - } - - public static void printHeader() { - System.out.println(ApplicationInfo.applicationVerName); - for (int i = 0; i < ApplicationInfo.applicationVerName.length(); i++) { - System.out.print("-"); - } - System.out.println(); - } - - public static void badArguments() { - badArguments(null); - } - - public static void badArguments(String command) { - System.err.println("Error: Bad Commandline Arguments!"); - printCmdLineUsage(); - System.exit(1); - } - - private static void setConfigurations(String cfgStr) { - String[] cfgs; - if (cfgStr.contains(",")) { - cfgs = cfgStr.split(","); - } else { - cfgs = new String[]{cfgStr}; - } - - for (String c : cfgs) { - String[] cp; - if (c.contains("=")) { - cp = c.split("="); - } else { - cp = new String[]{c, "1"}; - } - String key = cp[0]; - String value = cp[1]; - if (key.toLowerCase().equals("paralelSpeedUp".toLowerCase())) { - key = "parallelSpeedUp"; - } - for (ConfigurationItem item : commandlineConfigBoolean) { - if (key.toLowerCase().equals(item.getName().toLowerCase())) { - Boolean bValue = parseBooleanConfigValue(value); - if (bValue != null) { - System.out.println("Config " + item.getName() + " set to " + bValue); - item.set(bValue); - } - } - } - } - } - - private static Boolean parseBooleanConfigValue(String value) { - if (value == null) { - return null; - } - - Boolean bValue = null; - value = value.toLowerCase(); - if (value.equals("0") || value.equals("false") || value.equals("no") || value.equals("off")) { - bValue = false; - } - if (value.equals("1") || value.equals("true") || value.equals("yes") || value.equals("on")) { - bValue = true; - } - return bValue; - } - - private static void parseConfig(Stack args) { - if (args.isEmpty()) { - System.err.println("Config values expected"); - badArguments(); - } - setConfigurations(args.pop()); - } - - private static class Range { - - public Integer min; - - public Integer max; - - public Range(Integer min, Integer max) { - this.min = min; - this.max = max; - } - - public boolean contains(int index) { - int minimum = min == null ? Integer.MIN_VALUE : min; - int maximum = max == null ? Integer.MAX_VALUE : max; - - return index >= minimum && index <= maximum; - } - } - - private static class Selection { - - public List ranges; - - public Selection() { - this.ranges = new ArrayList<>(); - this.ranges.add(new Range(null, null)); - } - - public Selection(List ranges) { - this.ranges = ranges; - } - - public boolean contains(int index) { - for (Range r : ranges) { - if (r.contains(index)) { - return true; - } - } - return false; - } - } - - private static Selection parseSelect(Stack args) { - List ret = new ArrayList<>(); - if (args.isEmpty()) { - System.err.println("range parameter expected"); - badArguments(); - } - String range = args.pop(); - String[] ranges; - if (range.contains(",")) { - ranges = range.split(","); - } else { - ranges = new String[]{range}; - } - for (String r : ranges) { - Integer min = null; - Integer max = null; - if (r.contains("-")) { - String ps[] = r.split("\\-"); - if (ps.length != 2) { - System.err.println("invalid range"); - badArguments(); - } - try { - if (!"".equals(ps[0])) { - min = Integer.parseInt(ps[0]); - } - if (!"".equals(ps[1])) { - max = Integer.parseInt(ps[1]); - } - } catch (NumberFormatException nfe) { - System.err.println("invalid range"); - badArguments(); - } - } else { - try { - min = Integer.parseInt(r); - max = min; - } catch (NumberFormatException nfe) { - System.err.println("invalid range"); - badArguments(); - } - } - ret.add(new Range(min, max)); - } - return new Selection(ret); - } - - private static double parseZoom(Stack args) { - if (args.isEmpty()) { - System.err.println("zoom parameter expected"); - badArguments(); - } - try { - return Double.parseDouble(args.pop()); - } catch (NumberFormatException nfe) { - System.err.println("invalid zoom"); - badArguments(); - } - return 1; - } - - private static AbortRetryIgnoreHandler parseOnError(Stack args) { - int errorMode = AbortRetryIgnoreHandler.UNDEFINED; - int retryCount = 0; - - if (args.isEmpty()) { - System.err.println("onerror parameter expected"); - badArguments(); - } - String errorModeParameter = args.pop(); - switch (errorModeParameter) { - case "abort": - errorMode = AbortRetryIgnoreHandler.ABORT; - break; - case "retry": - errorMode = AbortRetryIgnoreHandler.RETRY; - if (args.isEmpty()) { - System.err.println("onerror retry count parameter expected"); - badArguments(); - } - - try { - retryCount = Integer.parseInt(args.pop()); - } catch (NumberFormatException nex) { - System.err.println("Bad retry count number"); - } - break; - case "ignore": - errorMode = AbortRetryIgnoreHandler.IGNORE; - break; - } - - return new ConsoleAbortRetryIgnoreHandler(errorMode, retryCount); - } - - private static void parseTimeout(Stack args) { - if (args.isEmpty()) { - System.err.println("timeout parameter expected"); - badArguments(); - } - try { - int timeout = Integer.parseInt(args.pop()); - Configuration.decompilationTimeoutSingleMethod.set(timeout); - } catch (NumberFormatException nex) { - System.err.println("Bad timeout value"); - } - } - - private static void parseExportTimeout(Stack args) { - if (args.isEmpty()) { - System.err.println("timeout parameter expected"); - badArguments(); - } - try { - int timeout = Integer.parseInt(args.pop()); - Configuration.exportTimeout.set(timeout); - } catch (NumberFormatException nex) { - System.err.println("Bad timeout value"); - } - } - - private static void parseExportFileTimeout(Stack args) { - if (args.isEmpty()) { - System.err.println("timeout parameter expected"); - badArguments(); - } - try { - int timeout = Integer.parseInt(args.pop()); - Configuration.decompilationTimeoutFile.set(timeout); - } catch (NumberFormatException nex) { - System.err.println("Bad timeout value"); - } - } - - private static void parseStdOut(Stack args) { - if (args.isEmpty()) { - System.err.println("stdOut parameter expected"); - badArguments(); - } - - stdOut = args.pop(); - } - - private static void parseStdErr(Stack args) { - if (args.isEmpty()) { - System.err.println("stdErr parameter expected"); - badArguments(); - } - - stdErr = args.pop(); - } - - private static void parseAffinity(Stack args) { - if (Platform.isWindows()) { - if (args.isEmpty()) { - System.err.println("affinity parameter expected"); - badArguments(); - } - try { - int affinityMask = Integer.parseInt(args.pop()); - Kernel32.INSTANCE.SetProcessAffinityMask(Kernel32.INSTANCE.GetCurrentProcess(), affinityMask); - } catch (NumberFormatException nex) { - System.err.println("Bad affinityMask value"); - } - } else { - System.err.println("Process affinity setting is only available on Windows platform."); - } - } - - private static void parsePriority(Stack args) { - if (Platform.isWindows()) { - if (args.isEmpty()) { - System.err.println("priority parameter expected"); - badArguments(); - } - String priority = args.pop(); - int priorityClass = 0; - switch (priority) { - case "low": - priorityClass = Kernel32.IDLE_PRIORITY_CLASS; - break; - case "belownormal": - priorityClass = Kernel32.BELOW_NORMAL_PRIORITY_CLASS; - break; - case "normal": - priorityClass = Kernel32.NORMAL_PRIORITY_CLASS; - break; - case "abovenormal": - priorityClass = Kernel32.ABOVE_NORMAL_PRIORITY_CLASS; - break; - case "high": - priorityClass = Kernel32.HIGH_PRIORITY_CLASS; - break; - case "realtime": - priorityClass = Kernel32.REALTIME_PRIORITY_CLASS; - break; - default: - System.err.println("Bad affinityMask value"); - } - if (priorityClass != 0) { - Kernel32.INSTANCE.SetPriorityClass(Kernel32.INSTANCE.GetCurrentProcess(), priorityClass); - } - } else { - System.err.println("Process priority setting is only available on Windows platform."); - } - } - - private static void parseDebugTool(Stack args) { - String cmd = args.pop().toLowerCase(); - switch (cmd) { - case "findtag": { - String folder = args.pop(); - String tagIdOrName = args.pop(); - int tagId; - try { - tagId = Integer.parseInt(tagIdOrName); - } catch (NumberFormatException e) { - tagId = Tag.getKnownClassesByName().get(tagIdOrName).getId(); - } - - PrintStream oldOut = System.out; - PrintStream oldErr = System.err; - PrintStream nullStream = new PrintStream(new OutputStream() { - @Override - public void write(int b) { - } - }); - System.setOut(nullStream); - System.setErr(nullStream); - Main.initLogging(Configuration.debugMode.get()); - - File[] files = new File(folder).listFiles(getSwfFilter()); - for (File file : files) { - SWFSourceInfo sourceInfo = new SWFSourceInfo(null, file.getAbsolutePath(), file.getName()); - try { - SWF swf = new SWF(new FileInputStream(file), sourceInfo.getFile(), sourceInfo.getFileTitle(), Configuration.parallelSpeedUp.get()); - swf.swfList = new SWFList(); - swf.swfList.sourceInfo = sourceInfo; - boolean found = false; - for (Tag tag : swf.tags) { - if (tag.getId() == tagId) { - found = true; - break; - } - } - - if (found) { - oldOut.println("Tag found in file: " + file.getAbsolutePath()); - } - } catch (IOException | InterruptedException ex) { - logger.log(Level.SEVERE, null, ex); - } - } - - System.setOut(oldOut); - System.setErr(oldErr); - Main.initLogging(Configuration.debugMode.get()); - break; - } - case "finderrorheader": { - String folder = args.pop(); - - PrintStream oldOut = System.out; - PrintStream oldErr = System.err; - PrintStream nullStream = new PrintStream(new OutputStream() { - @Override - public void write(int b) { - } - }); - System.setOut(nullStream); - System.setErr(nullStream); - Main.initLogging(Configuration.debugMode.get()); - - File[] files = new File(folder).listFiles(getSwfFilter()); - for (File file : files) { - SWFSourceInfo sourceInfo = new SWFSourceInfo(null, file.getAbsolutePath(), file.getName()); - try { - SWF swf = new SWF(new FileInputStream(file), sourceInfo.getFile(), sourceInfo.getFileTitle(), Configuration.parallelSpeedUp.get()); - swf.swfList = new SWFList(); - swf.swfList.sourceInfo = sourceInfo; - boolean found = false; - for (Tag tag : swf.tags) { - if (tag instanceof JPEGTablesTag) { - JPEGTablesTag jtt = (JPEGTablesTag) tag; - if (ImageTag.hasErrorHeader(jtt.jpegData)) { - found = true; - break; - } - } else if (tag instanceof DefineBitsJPEG2Tag) { - DefineBitsJPEG2Tag jpeg = (DefineBitsJPEG2Tag) tag; - if (ImageTag.hasErrorHeader(jpeg.imageData)) { - found = true; - break; - } - } else if (tag instanceof DefineBitsJPEG3Tag) { - DefineBitsJPEG3Tag jpeg = (DefineBitsJPEG3Tag) tag; - if (ImageTag.hasErrorHeader(jpeg.imageData)) { - found = true; - break; - } - } - } - - if (found) { - oldOut.println("Tag found in file: " + file.getAbsolutePath()); - } - } catch (IOException | InterruptedException ex) { - logger.log(Level.SEVERE, null, ex); - } - } - - System.setOut(oldOut); - System.setErr(oldErr); - Main.initLogging(Configuration.debugMode.get()); - break; - } - } - } - - private static void parseCompareResources(Stack args) { - String revision = args.pop().toLowerCase(); - String revision2 = null; - if (!args.isEmpty()) { - revision2 = args.pop(); - } - - CheckResources.compareResources(System.out, revision, revision2); - } - - private static void parseProxy(Stack args) { - int port = 55555; - String portStr = args.peek(); - if (portStr != null && portStr.startsWith("-P")) { - args.pop(); - try { - port = Integer.parseInt(portStr.substring(2)); - } catch (NumberFormatException nex) { - System.err.println("Bad port number"); - } - } - Main.startProxy(port); - } - - private static List parseSelectClassOld(Stack args) { - List ret = new ArrayList<>(); - if (!args.isEmpty() && args.peek().equals("-selectas3class")) { - args.pop(); - while (!args.isEmpty()) { - ret.add(args.pop()); - } - System.err.println("WARNING: Using deprecated -selectas3class parameter. Please use -selectclass instead. See --help for usage."); - } - return ret; - - } - - private static List parseSelectClass(Stack args) { - if (args.size() < 1) { - badArguments(); - } - List ret = new ArrayList<>(); - String classesStr = args.pop(); - String classes[]; - if (classesStr.contains(",")) { - classes = classesStr.split(","); - } else { - classes = new String[]{classesStr}; - } - ret.addAll(Arrays.asList(classes)); - return ret; - - } - - private static void parseExport(List selectionClasses, Selection selection, Selection selectionIds, Stack args, AbortRetryIgnoreHandler handler, Level traceLevel, Map formats, double zoom) { - if (args.size() < 3) { - badArguments(); - } - String[] validExportItems = new String[]{ - "script", - "image", - "shape", - "morphshape", - "movie", - "font", - "frame", - "sprite", - "button", - "sound", - "binarydata", - "text", - "all", - "fla", - "xfl" - }; - - String[] removedExportFormats = new String[]{ - "as", - "pcode", - "hex", - "pcodehex", - "all_as", - "all_pcode", - "all_pcodehex", - "all_hex", - "textplain" - }; - - if (handler == null) { - handler = new ConsoleAbortRetryIgnoreHandler(AbortRetryIgnoreHandler.UNDEFINED, 0); - } - String exportFormatString = args.pop().toLowerCase(); - List exportFormats = Arrays.asList(exportFormatString.split(",")); - long startTime = System.currentTimeMillis(); - - File outDirBase = new File(args.pop()); - File inFileOrFolder = new File(args.pop()); - if (!inFileOrFolder.exists()) { - System.err.println("Input SWF file does not exist!"); - badArguments(); - } - printHeader(); - boolean exportOK = true; - - List as3classes = new ArrayList<>(); - if (selectionClasses != null) { - as3classes.addAll(selectionClasses); - } - - try { - File[] inFiles; - boolean singleFile = true; - if (inFileOrFolder.isDirectory()) { - singleFile = false; - inFiles = inFileOrFolder.listFiles(getSwfFilter()); - } else { - inFiles = new File[]{inFileOrFolder}; - } - - for (File inFile : inFiles) { - String inFileName = Path.getFileNameWithoutExtension(inFile); - if (stdOut != null) { - System.setOut(new PrintStream(new FileOutputStream(stdOut.replace("{swfFile}", inFileName), true))); - } - - if (stdErr != null) { - System.setErr(new PrintStream(new FileOutputStream(stdErr.replace("{swfFile}", inFileName), true))); - Main.initLogging(Configuration.debugMode.get()); - } - - long startTimeSwf = 0; - if (!singleFile) { - startTimeSwf = System.currentTimeMillis(); - System.out.println("Start exporting " + inFile.getName()); - } - - SWFSourceInfo sourceInfo = new SWFSourceInfo(null, inFile.getAbsolutePath(), inFile.getName()); - SWF swf; - try { - swf = new SWF(new FileInputStream(inFile), sourceInfo.getFile(), sourceInfo.getFileTitle(), Configuration.parallelSpeedUp.get()); - } catch (SwfOpenException ex) { - logger.log(Level.SEVERE, "Failed to open swf: " + inFile.getName(), ex); - continue; - } - - swf.swfList = new SWFList(); - swf.swfList.sourceInfo = sourceInfo; - String outDir = outDirBase.getAbsolutePath(); - if (!singleFile) { - outDir = Path.combine(outDir, inFile.getName()); - } - - List extags = new ArrayList<>(); - for (Tag t : swf.tags) { - if (t instanceof CharacterIdTag) { - CharacterIdTag c = (CharacterIdTag) t; - if (selectionIds.contains(c.getCharacterId())) { - extags.add(t); - } - } else { - if (selectionIds.contains(0)) { - extags.add(t); - } - } - } - - final Level level = traceLevel; - swf.addEventListener(new EventListener() { - @Override - public void handleExportingEvent(String type, int index, int count, Object data) { - if (level.intValue() <= Level.FINE.intValue()) { - String text = "Exporting "; - if (type != null && type.length() > 0) { - text += type + " "; - } - System.out.println(text + index + "/" + count + " " + data); - } - } - - @Override - public void handleExportedEvent(String type, int index, int count, Object data) { - String text = "Exported "; - if (type != null && type.length() > 0) { - text += type + " "; - } - System.out.println(text + index + "/" + count + " " + data); - } - - @Override - public void handleEvent(String event, Object data) { - } - }); - - // First check all the specified export formats - for (String exportFormat : exportFormats) { - if (Arrays.asList(removedExportFormats).contains(exportFormat)) { - System.err.println("Error: Export format : " + exportFormat + " was REMOVED. Run application with --help parameter to see available formats."); - System.exit(1); - } else if (!Arrays.asList(validExportItems).contains(exportFormat)) { - System.err.println("Invalid export item:" + exportFormat); - badArguments(); - } - } - - // Here the exportFormats array should contain only validitems - commandLineMode = true; - boolean exportAll = exportFormats.contains("all"); - boolean multipleExportTypes = exportAll || exportFormats.size() > 1; - EventListener evl = swf.getExportEventListener(); - - if (exportAll || exportFormats.contains("image")) { - System.out.println("Exporting images..."); - new ImageExporter().exportImages(handler, outDir + (multipleExportTypes ? File.separator + ImageExportSettings.EXPORT_FOLDER_NAME : ""), extags, new ImageExportSettings(enumFromStr(formats.get("image"), ImageExportMode.class)), evl); - } - - if (exportAll || exportFormats.contains("shape")) { - System.out.println("Exporting shapes..."); - new ShapeExporter().exportShapes(handler, outDir + (multipleExportTypes ? File.separator + ShapeExportSettings.EXPORT_FOLDER_NAME : ""), extags, new ShapeExportSettings(enumFromStr(formats.get("shape"), ShapeExportMode.class), zoom), evl); - } - - if (exportAll || exportFormats.contains("morphshape")) { - System.out.println("Exporting morphshapes..."); - new MorphShapeExporter().exportMorphShapes(handler, outDir + (multipleExportTypes ? File.separator + MorphShapeExportSettings.EXPORT_FOLDER_NAME : ""), extags, new MorphShapeExportSettings(enumFromStr(formats.get("morphshape"), MorphShapeExportMode.class), zoom), evl); - } - - if (exportAll || exportFormats.contains("movie")) { - System.out.println("Exporting movies..."); - new MovieExporter().exportMovies(handler, outDir + (multipleExportTypes ? File.separator + MovieExportSettings.EXPORT_FOLDER_NAME : ""), extags, new MovieExportSettings(enumFromStr(formats.get("movie"), MovieExportMode.class)), evl); - } - - if (exportAll || exportFormats.contains("font")) { - System.out.println("Exporting fonts..."); - new FontExporter().exportFonts(handler, outDir + (multipleExportTypes ? File.separator + FontExportSettings.EXPORT_FOLDER_NAME : ""), extags, new FontExportSettings(enumFromStr(formats.get("font"), FontExportMode.class)), evl); - } - - if (exportAll || exportFormats.contains("sound")) { - System.out.println("Exporting sounds..."); - new SoundExporter().exportSounds(handler, outDir + (multipleExportTypes ? File.separator + SoundExportSettings.EXPORT_FOLDER_NAME : ""), extags, new SoundExportSettings(enumFromStr(formats.get("sound"), SoundExportMode.class)), evl); - } - - if (exportAll || exportFormats.contains("binarydata")) { - System.out.println("Exporting binaryData..."); - new BinaryDataExporter().exportBinaryData(handler, outDir + (multipleExportTypes ? File.separator + BinaryDataExportSettings.EXPORT_FOLDER_NAME : ""), extags, new BinaryDataExportSettings(enumFromStr(formats.get("binarydata"), BinaryDataExportMode.class)), evl); - } - - if (exportAll || exportFormats.contains("text")) { - System.out.println("Exporting texts..."); - Boolean singleTextFile = parseBooleanConfigValue(formats.get("singletext")); - if (singleTextFile == null) { - singleTextFile = Configuration.textExportSingleFile.get(); - } - new TextExporter().exportTexts(handler, outDir + (multipleExportTypes ? File.separator + TextExportSettings.EXPORT_FOLDER_NAME : ""), extags, new TextExportSettings(enumFromStr(formats.get("text"), TextExportMode.class), singleTextFile, zoom), evl); - } - - FrameExporter frameExporter = new FrameExporter(); - - if (exportAll || exportFormats.contains("frame")) { - System.out.println("Exporting frames..."); - List frames = new ArrayList<>(); - for (int i = 0; i < swf.frameCount; i++) { - if (selection.contains(i + 1)) { - frames.add(i); - } - } - FrameExportSettings fes = new FrameExportSettings(enumFromStr(formats.get("frame"), FrameExportMode.class), zoom); - frameExporter.exportFrames(handler, outDir + (multipleExportTypes ? File.separator + FrameExportSettings.EXPORT_FOLDER_NAME : ""), swf, 0, frames, fes, evl); - } - - if (exportAll || exportFormats.contains("sprite")) { - System.out.println("Exporting sprite..."); - SpriteExportSettings ses = new SpriteExportSettings(enumFromStr(formats.get("sprite"), SpriteExportMode.class), zoom); - for (CharacterTag c : swf.getCharacters().values()) { - if (c instanceof DefineSpriteTag) { - frameExporter.exportFrames(handler, outDir + (multipleExportTypes ? File.separator + SpriteExportSettings.EXPORT_FOLDER_NAME : ""), swf, c.getCharacterId(), null, ses, evl); - } - } - } - - if (exportAll || exportFormats.contains("button")) { - System.out.println("Exporting buttons..."); - ButtonExportSettings bes = new ButtonExportSettings(enumFromStr(formats.get("button"), ButtonExportMode.class), zoom); - for (CharacterTag c : swf.getCharacters().values()) { - if (c instanceof ButtonTag) { - List frameNums = new ArrayList<>(); - frameNums.add(0); // todo: export all frames - frameExporter.exportFrames(handler, outDir + (multipleExportTypes ? File.separator + ButtonExportSettings.EXPORT_FOLDER_NAME : ""), swf, c.getCharacterId(), frameNums, bes, evl); - } - } - } - - boolean parallel = Configuration.parallelSpeedUp.get(); - Boolean singleScriptFile = parseBooleanConfigValue(formats.get("singlescript")); - if (singleScriptFile == null) { - singleScriptFile = Configuration.scriptExportSingleFile.get(); - } - - if (parallel && singleScriptFile) { - System.out.println("Single file script export is not supported with enabled parallel speedup"); - singleScriptFile = false; - } - - ScriptExportSettings scriptExportSettings = new ScriptExportSettings(enumFromStr(formats.get("script"), ScriptExportMode.class), singleScriptFile); - if (exportAll || exportFormats.contains("script")) { - System.out.println("Exporting scripts..."); - if (as3classes.isEmpty()) { - as3classes = parseSelectClassOld(args); - } - - String scriptsFolder = Path.combine(outDir, ScriptExportSettings.EXPORT_FOLDER_NAME); - Path.createDirectorySafe(new File(scriptsFolder)); - String singleFileName = Path.combine(scriptsFolder, swf.getShortFileName() + scriptExportSettings.getFileExtension()); - try (FileTextWriter writer = scriptExportSettings.singleFile ? new FileTextWriter(Configuration.getCodeFormatting(), new FileOutputStream(singleFileName)) : null) { - scriptExportSettings.singleFileWriter = writer; - if (!as3classes.isEmpty()) { - for (String as3class : as3classes) { - exportOK = exportOK && swf.exportAS3Class(as3class, scriptsFolder, scriptExportSettings, parallel, evl); - } - } else { - exportOK = exportOK && swf.exportActionScript(handler, scriptsFolder, scriptExportSettings, parallel, evl) != null; - } - } - } - - if (exportFormats.contains("fla")) { - System.out.println("Exporting FLA..."); - FLAVersion flaVersion = FLAVersion.fromString(formats.get("fla")); - if (flaVersion == null) { - flaVersion = FLAVersion.CS6; //Defaults to CS6 - } - swf.exportFla(handler, outDir + (multipleExportTypes ? File.separator + "fla" : ""), inFile.getName(), ApplicationInfo.APPLICATION_NAME, ApplicationInfo.applicationVerName, ApplicationInfo.version, Configuration.parallelSpeedUp.get(), flaVersion); - } - - if (exportFormats.contains("xfl")) { - System.out.println("Exporting XFL..."); - FLAVersion xflVersion = FLAVersion.fromString(formats.get("xfl")); - if (xflVersion == null) { - xflVersion = FLAVersion.CS6; //Defaults to CS6 - } - swf.exportXfl(handler, outDir + (multipleExportTypes ? File.separator + "xfl" : ""), inFile.getName(), ApplicationInfo.APPLICATION_NAME, ApplicationInfo.applicationVerName, ApplicationInfo.version, Configuration.parallelSpeedUp.get(), xflVersion); - } - - if (!singleFile) { - long stopTimeSwf = System.currentTimeMillis(); - long time = stopTimeSwf - startTimeSwf; - System.out.println("Export finished: " + inFile.getName() + " Export time: " + Helper.formatTimeSec(time)); - } - - swf.clearAllCache(); - } - } catch (OutOfMemoryError | Exception ex) { - System.err.print("FAIL: Exporting Failed on Exception - "); - logger.log(Level.SEVERE, null, ex); - System.exit(1); - } - - long stopTime = System.currentTimeMillis(); - long time = stopTime - startTime; - System.out.println("Export finished. Total export time: " + Helper.formatTimeSec(time)); - if (exportOK) { - System.out.println("OK"); - System.exit(0); - } else { - System.err.println("FAIL"); - System.exit(1); - } - } - - private static void parseCompress(Stack args) { - if (args.size() < 2) { - badArguments(); - } - - try { - try (InputStream fis = new BufferedInputStream(new FileInputStream(args.pop())); - OutputStream fos = new BufferedOutputStream(new FileOutputStream(args.pop()))) { - SWFCompression compression = SWFCompression.ZLIB; - String compressionString = !args.isEmpty() ? args.pop() : null; - if (compressionString != null) { - switch (compressionString.toLowerCase()) { - case "zlib": - compression = SWFCompression.ZLIB; - break; - case "lzma": - compression = SWFCompression.LZMA; - break; - default: - System.out.println("Unsupported compression method: " + compressionString); - System.exit(0); - break; - } - } - - if (SWF.compress(fis, fos, compression)) { - System.out.println("OK"); - } else { - System.err.println("FAIL"); - } - } catch (FileNotFoundException ex) { - System.err.println("File not found."); - } - } catch (IOException ex) { - logger.log(Level.SEVERE, null, ex); - } - - System.exit(0); - } - - private static void parseDecompress(Stack args) { - if (args.size() < 2) { - badArguments(); - } - - try { - try (InputStream fis = new BufferedInputStream(new FileInputStream(args.pop())); - OutputStream fos = new BufferedOutputStream(new FileOutputStream(args.pop()))) { - if (SWF.decompress(fis, fos)) { - System.out.println("OK"); - System.exit(0); - } else { - System.err.println("FAIL"); - System.exit(1); - } - } catch (FileNotFoundException ex) { - System.err.println("File not found."); - } - } catch (IOException ex) { - logger.log(Level.SEVERE, null, ex); - } - - System.exit(0); - } - - private static void parseSwf2Xml(Stack args) { - if (args.size() < 2) { - badArguments(); - } - - try { - try (FileInputStream is = new FileInputStream(args.pop())) { - SWF swf = new SWF(is, Configuration.parallelSpeedUp.get()); - new SwfXmlExporter().exportXml(swf, new File(args.pop())); - } catch (FileNotFoundException ex) { - System.err.println("File not found."); - } catch (InterruptedException ex) { - logger.log(Level.SEVERE, null, ex); - } - } catch (IOException ex) { - logger.log(Level.SEVERE, null, ex); - } - - System.exit(0); - } - - private static void parseXml2Swf(Stack args) { - if (args.size() < 2) { - badArguments(); - } - - try { - String xml = Helper.readTextFile(args.pop()); - SWF swf = new SWF(); - new SwfXmlImporter().importSwf(swf, xml); - try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(new File(args.pop())))) { - swf.saveTo(new BufferedOutputStream(fos)); - } - } catch (IOException ex) { - logger.log(Level.SEVERE, null, ex); - } - - System.exit(0); - } - - private static void parseExtract(Stack args) { - if (args.size() < 1) { - badArguments(); - } - - String fileName = args.pop(); - SearchMode mode = SearchMode.ALL; - - boolean noCheck = false; - String output = null; - - if (args.size() > 0 && args.peek().toLowerCase().equals("-o")) { - args.pop(); - if (args.size() < 1) { - badArguments(); - } - output = args.pop(); - } - - if (args.size() > 0 && args.peek().toLowerCase().equals("nocheck")) { - noCheck = true; - args.pop(); - } - - if (args.size() > 0) { - String modeStr = args.pop().toLowerCase(); - switch (modeStr) { - case "biggest": - mode = SearchMode.BIGGEST; - break; - case "smallest": - mode = SearchMode.SMALLEST; - break; - case "first": - mode = SearchMode.FIRST; - break; - case "last": - mode = SearchMode.LAST; - break; - } - } - - try { - SWFSourceInfo sourceInfo = new SWFSourceInfo(null, fileName, null); - if (!sourceInfo.isBundle()) { - System.err.println("Error: should be a bundle. (ZIP or non SWF binary file)"); - System.exit(1); - } - SWFBundle bundle = sourceInfo.getBundle(noCheck, mode); - List> streamsToExtract = new ArrayList<>(); - for (Map.Entry streamEntry : bundle.getAll().entrySet()) { - InputStream stream = streamEntry.getValue(); - stream.reset(); - streamsToExtract.add(streamEntry); - } - - for (Map.Entry streamEntry : streamsToExtract) { - InputStream stream = streamEntry.getValue(); - stream.reset(); - String fileNameOut; - if (mode != SearchMode.ALL) { - if (output == null) { - fileNameOut = Path.getFileNameWithoutExtension(new File(fileName)) + ".swf"; - } else { - fileNameOut = output; - } - } else { - fileNameOut = streamEntry.getKey() + ".swf"; - if (output != null) { - fileNameOut = Path.combine(output, fileNameOut); - } - } - - try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(fileNameOut))) { - byte[] swfData = new byte[stream.available()]; - int cnt = stream.read(swfData); - fos.write(swfData, 0, cnt); - } - } - } catch (IOException ex) { - logger.log(Level.SEVERE, null, ex); - } - - System.exit(0); - } - - private static void parseRenameInvalidIdentifiers(Stack args) { - if (args.size() < 3) { - badArguments(); - } - - String renameTypeStr = args.pop(); - RenameType renameType; - switch (renameTypeStr.toLowerCase()) { - case "typenumber": - renameType = RenameType.TYPENUMBER; - break; - case "randomword": - renameType = RenameType.RANDOMWORD; - break; - default: - System.err.println("Invalid rename type:" + renameTypeStr); - badArguments(); - return; - } - - try { - try (InputStream fis = new BufferedInputStream(new FileInputStream(args.pop())); - OutputStream fos = new BufferedOutputStream(new FileOutputStream(args.pop()))) { - if (SWF.renameInvalidIdentifiers(renameType, fis, fos)) { - System.out.println("OK"); - System.exit(0); - } else { - System.err.println("FAIL"); - System.exit(1); - } - } catch (FileNotFoundException ex) { - System.err.println("File not found."); - } - } catch (IOException ex) { - logger.log(Level.SEVERE, null, ex); - } - - System.exit(0); - } - - private static Map parseFormat(Stack args) { - if (args.size() < 1) { - badArguments(); - } - String fmtStr = args.pop(); - String[] fmts; - if (fmtStr.contains(",")) { - fmts = fmtStr.split(","); - } else { - fmts = new String[]{fmtStr}; - } - Map ret = new HashMap<>(); - for (String fmt : fmts) { - String[] parts = fmt.split(":"); - ret.put(parts[0].toLowerCase(), parts[1].toLowerCase()); - } - return ret; - } - - private static void parseFlashPaperToPdf(Selection selection, double zoom, Stack args) { - if (args.size() < 2) { - badArguments(); - } - File inFile = new File(args.pop()); - File outFile = new File(args.pop()); - printHeader(); - - try (FileInputStream is = new FileInputStream(inFile)) { - - PDFJob job = null; - - SWF swf = new SWF(is, Configuration.parallelSpeedUp.get()); - int totalPages = 0; - - for (Tag t : swf.tags) { - if (t instanceof DefineSpriteTag) { - DefineSpriteTag ds = (DefineSpriteTag) t; - if ("page1".equals(ds.getExportName())) { - totalPages = 1; - } else { - if (totalPages > 0) { - totalPages++; - } - } - } - } - - int page = 0; - - for (Tag t : swf.tags) { - if (t instanceof DefineSpriteTag) { - DefineSpriteTag ds = (DefineSpriteTag) t; - if ("page1".equals(ds.getExportName())) { - page = 1; - job = new PDFJob(new BufferedOutputStream(new FileOutputStream(outFile))); - } else { - if (page > 0) { - page++; - } - } - if (("page" + page).equals(ds.getExportName())) { - if (!selection.contains(page)) { - continue; - } - System.out.print("Page " + page + "/" + totalPages + "..."); - RECT displayRect = new RECT(ds.getTimeline().displayRect); - //displayRect.Xmax *= zoom; - //displayRect.Ymax *= zoom; - Matrix m = new Matrix(); - //m.scale(zoom); - BufferedImage img = SWF.frameToImageGet(ds.getTimeline(), 0, 0, null, 0, displayRect, m, new ColorTransform(), Color.white, false, zoom).getBufferedImage(); - PageFormat pf = new PageFormat(); - pf.setOrientation(PageFormat.PORTRAIT); - Paper p = new Paper(); - p.setSize(img.getWidth(), img.getHeight()); - pf.setPaper(p); - Graphics g = job.getGraphics(pf); - g.drawImage(img, 0, 0, img.getWidth(), img.getHeight(), null); - g.dispose(); - System.out.println("OK"); - - } - } - } - - if (job == null) { - System.err.println("No pages found. Maybe it is not a FlashPaper file"); - System.exit(2); - } - job.end(); - - } catch (FileNotFoundException ex) { - System.err.println("File not found"); - System.exit(1); - } catch (IOException | InterruptedException ex) { - System.err.println("I/O error during reading"); - System.exit(2); - } - System.exit(0); - } - - private static void parseReplace(Stack args) { - if (args.size() < 4) { - badArguments(); - } - - File inFile = new File(args.pop()); - File outFile = new File(args.pop()); - try { - try (FileInputStream is = new FileInputStream(inFile)) { - SWF swf = new SWF(is, Configuration.parallelSpeedUp.get()); - while (true) { - String objectToReplace = args.pop(); - - if (objectToReplace.matches("\\d+")) { - // replace character tag - int characterId = 0; - try { - characterId = Integer.parseInt(objectToReplace); - } catch (NumberFormatException nfe) { - System.err.println("CharacterId should be integer"); - System.exit(1); - } - if (!swf.getCharacters().containsKey(characterId)) { - System.err.println("CharacterId does not exist"); - System.exit(1); - } - - CharacterTag characterTag = swf.getCharacter(characterId); - String repFile = args.pop(); - byte[] data = Helper.readFile(repFile); - if (characterTag instanceof DefineBinaryDataTag) { - DefineBinaryDataTag defineBinaryData = (DefineBinaryDataTag) characterTag; - new BinaryDataImporter().importData(defineBinaryData, data); - } else if (characterTag instanceof ImageTag) { - ImageTag imageTag = (ImageTag) characterTag; - new ImageImporter().importImage(imageTag, data); - } else if (characterTag instanceof ShapeTag) { - ShapeTag shapeTag = (ShapeTag) characterTag; - new ShapeImporter().importImage(shapeTag, data); - } else if (characterTag instanceof SoundTag) { - SoundTag st = (SoundTag) characterTag; - int soundFormat = SoundFormat.FORMAT_UNCOMPRESSED_LITTLE_ENDIAN; - if (repFile.toLowerCase().endsWith(".mp3")) { - soundFormat = SoundFormat.FORMAT_MP3; - } - boolean ok = st.setSound(new ByteArrayInputStream(data), soundFormat); - if (!ok) { - System.err.println("Import FAILED. Maybe unsuppoted media type? Only MP3 and uncompressed WAV are available."); - System.exit(1); - } - } else { - System.err.println("The specified tag type is not supported for import"); - System.exit(1); - } - } else { - Map asms = swf.getASMs(false); - boolean found = false; - if (asms.containsKey(objectToReplace)) { - found = true; - // replace AS1/2 - String repFile = args.pop(); - String repText = Helper.readTextFile(repFile); - ASMSource src = asms.get(objectToReplace); - if (Path.getExtension(repFile).equals(".as")) { - replaceAS2(repText, src); - } else { - replaceAS2PCode(repText, src); - } - } else { - List packs = swf.getAS3Packs(); - for (ScriptPack entry : packs) { - if (entry.getClassPath().toString().equals(objectToReplace)) { - found = true; - // replace AS3 - String repFile = args.pop(); - String repText = Helper.readTextFile(repFile); - ScriptPack pack = entry; - if (Path.getExtension(repFile).equals(".as")) { - replaceAS3(repText, pack); - } else { - // todo: get traits - if (args.isEmpty()) { - badArguments(); - } - int bodyIndex = Integer.parseInt(args.pop()); - //int classIndex = 0; - //int traitId = 0; - Trait trait = null; //abc.findTraitByTraitId(classIndex, traitId); - replaceAS3PCode(repText, pack.abc, bodyIndex, trait); - } - } - } - } - - if (!found) { - System.err.println(objectToReplace + " is not reocginized as a CharacterId or a script name."); - System.exit(1); - } - } - - if (args.isEmpty() || args.peek().startsWith("-")) { - break; - } - } - - try { - try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(outFile))) { - swf.saveTo(fos); - } - } catch (IOException e) { - System.err.println("I/O error during writing"); - System.exit(2); - } - } - } catch (IOException | InterruptedException e) { - System.err.println("I/O error during reading"); - System.exit(2); - } - } - - private static void replaceAS2PCode(String text, ASMSource src) throws IOException, InterruptedException { - System.out.println("Replace AS1/2 PCode"); - if (text.trim().startsWith(Helper.hexData)) { - src.setActionBytes(Helper.getBytesFromHexaText(text)); - } else { - try { - src.setActions(ASMParser.parse(0, true, text, src.getSwf().version, false)); - } catch (ActionParseException ex) { - System.err.println("%error% on line %line%".replace("%error%", ex.text).replace("%line%", Long.toString(ex.line))); - System.exit(1); - } - } - src.setModified(); - } - - private static void replaceAS2(String as, ASMSource src) throws IOException, InterruptedException { - System.out.println("Replace AS1/2"); - System.out.println("Warning: This feature is EXPERIMENTAL"); - ActionScript2Parser par = new ActionScript2Parser(src.getSwf().version); - try { - src.setActions(par.actionsFromString(as)); - } catch (ActionParseException ex) { - System.err.println("%error% on line %line%".replace("%error%", ex.text).replace("%line%", Long.toString(ex.line))); - System.exit(1); - } catch (CompilationException ex) { - System.err.println("%error% on line %line%".replace("%error%", ex.text).replace("%line%", Long.toString(ex.line))); - System.exit(1); - } - src.setModified(); - } - - private static void replaceAS3PCode(String text, ABC abc, int bodyIndex, Trait trait) throws IOException, InterruptedException { - System.out.println("Replace AS3 PCode"); - if (text.trim().startsWith(Helper.hexData)) { - byte[] data = Helper.getBytesFromHexaText(text); - MethodBody mb = abc.bodies.get(bodyIndex); - mb.setCodeBytes(data); - } else { - try { - AVM2Code acode = ASM3Parser.parse(new StringReader(text), abc.constants, trait, new MissingSymbolHandler() { - //no longer ask for adding new constants - @Override - public boolean missingString(String value) { - return true; - } - - @Override - public boolean missingInt(long value) { - return true; - } - - @Override - public boolean missingUInt(long value) { - return true; - } - - @Override - public boolean missingDouble(double value) { - return true; - } - }, abc.bodies.get(bodyIndex), abc.method_info.get(abc.bodies.get(bodyIndex).method_info)); - //acode.getBytes(abc.bodies.get(bodyIndex).getCodeBytes()); - abc.bodies.get(bodyIndex).setCode(acode); - } catch (AVM2ParseException ex) { - System.err.println("%error% on line %line%".replace("%error%", ex.text).replace("%line%", Long.toString(ex.line))); - System.exit(1); - } - } - ((Tag) abc.parentTag).setModified(true); - } - - private static void replaceAS3(String as, ScriptPack pack) throws IOException, InterruptedException { - System.out.println("Replace AS3"); - System.out.println("Warning: This feature is EXPERIMENTAL"); - File swc = Configuration.getPlayerSWC(); - if (swc == null) { - final String adobePage = "http://www.adobe.com/support/flashplayer/downloads.html"; - System.err.println("For ActionScript 3 direct editation, a library called \"PlayerGlobal.swc\" needs to be downloaded from Adobe homepage:"); - System.err.println(adobePage); - System.err.println("Download the library called PlayerGlobal(.swc), and place it to directory"); - System.err.println(Configuration.getFlashLibPath().getAbsolutePath()); - System.exit(1); - } - - try { - pack.abc.replaceScriptPack(pack, as); - } catch (AVM2ParseException ex) { - System.err.println("%error% on line %line%".replace("%error%", ex.text).replace("%line%", Long.toString(ex.line))); - System.exit(1); - } catch (CompilationException ex) { - System.err.println("%error% on line %line%".replace("%error%", ex.text).replace("%line%", Long.toString(ex.line))); - System.exit(1); - } - } - - private static void parseDumpSwf(Stack args) { - if (args.isEmpty()) { - badArguments(); - } - try { - Configuration.dumpTags.set(true); - Configuration.parallelSpeedUp.set(false); - SWFSourceInfo sourceInfo = new SWFSourceInfo(null, args.pop(), null); - Main.parseSWF(sourceInfo); - } catch (Exception ex) { - logger.log(Level.SEVERE, null, ex); - System.exit(1); - } - System.exit(0); - } - - private static void parseDumpAS2(Stack args) { - if (args.isEmpty()) { - badArguments(); - } - File file = new File(args.pop()); - try { - try (FileInputStream is = new FileInputStream(file)) { - SWF swf = new SWF(is, Configuration.parallelSpeedUp.get()); - Map asms = swf.getASMs(false); - for (String as2 : asms.keySet()) { - System.out.println(as2); - } - } - } catch (IOException | InterruptedException e) { - System.err.println("I/O error during reading"); - System.exit(2); - } - } - - private static void parseDumpAS3(Stack args) { - if (args.isEmpty()) { - badArguments(); - } - File file = new File(args.pop()); - try { - try (FileInputStream is = new FileInputStream(file)) { - SWF swf = new SWF(is, Configuration.parallelSpeedUp.get()); - List packs = swf.getAS3Packs(); - for (ScriptPack entry : packs) { - System.out.println(entry.getClassPath().toString() + " " + entry.scriptIndex); - } - } - } catch (IOException | InterruptedException e) { - System.err.println("I/O error during reading"); - System.exit(2); - } - } - - private static FilenameFilter getSwfFilter() { - return (File dir, String name) -> name.toLowerCase().endsWith(".swf"); - } - - private static E enumFromStr(String str, Class cls) { - E[] vals = cls.getEnumConstants(); - if (str == null) { - return vals[0]; - } - for (E e : vals) { - if (e.toString().toLowerCase().replace("_", "").equals(str.toLowerCase().replace("_", ""))) { - return e; - } - } - return vals[0]; - } -} +/* + * Copyright (C) 2010-2015 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.console; + +import com.jpexs.decompiler.flash.AbortRetryIgnoreHandler; +import com.jpexs.decompiler.flash.ApplicationInfo; +import com.jpexs.decompiler.flash.EventListener; +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.SWFBundle; +import com.jpexs.decompiler.flash.SWFCompression; +import com.jpexs.decompiler.flash.SWFSourceInfo; +import com.jpexs.decompiler.flash.SearchMode; +import com.jpexs.decompiler.flash.SwfOpenException; +import com.jpexs.decompiler.flash.abc.ABC; +import com.jpexs.decompiler.flash.abc.RenameType; +import com.jpexs.decompiler.flash.abc.ScriptPack; +import com.jpexs.decompiler.flash.abc.avm2.AVM2Code; +import com.jpexs.decompiler.flash.abc.avm2.parser.AVM2ParseException; +import com.jpexs.decompiler.flash.abc.avm2.parser.pcode.ASM3Parser; +import com.jpexs.decompiler.flash.abc.avm2.parser.pcode.MissingSymbolHandler; +import com.jpexs.decompiler.flash.abc.avm2.parser.script.ActionScript3Parser; +import com.jpexs.decompiler.flash.abc.types.MethodBody; +import com.jpexs.decompiler.flash.abc.types.traits.Trait; +import com.jpexs.decompiler.flash.action.parser.ActionParseException; +import com.jpexs.decompiler.flash.action.parser.pcode.ASMParser; +import com.jpexs.decompiler.flash.action.parser.script.ActionScript2Parser; +import com.jpexs.decompiler.flash.configuration.Configuration; +import com.jpexs.decompiler.flash.configuration.ConfigurationItem; +import com.jpexs.decompiler.flash.exporters.BinaryDataExporter; +import com.jpexs.decompiler.flash.exporters.FontExporter; +import com.jpexs.decompiler.flash.exporters.FrameExporter; +import com.jpexs.decompiler.flash.exporters.ImageExporter; +import com.jpexs.decompiler.flash.exporters.MorphShapeExporter; +import com.jpexs.decompiler.flash.exporters.MovieExporter; +import com.jpexs.decompiler.flash.exporters.ShapeExporter; +import com.jpexs.decompiler.flash.exporters.SoundExporter; +import com.jpexs.decompiler.flash.exporters.TextExporter; +import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; +import com.jpexs.decompiler.flash.exporters.modes.BinaryDataExportMode; +import com.jpexs.decompiler.flash.exporters.modes.ButtonExportMode; +import com.jpexs.decompiler.flash.exporters.modes.FontExportMode; +import com.jpexs.decompiler.flash.exporters.modes.FrameExportMode; +import com.jpexs.decompiler.flash.exporters.modes.ImageExportMode; +import com.jpexs.decompiler.flash.exporters.modes.MorphShapeExportMode; +import com.jpexs.decompiler.flash.exporters.modes.MovieExportMode; +import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; +import com.jpexs.decompiler.flash.exporters.modes.ShapeExportMode; +import com.jpexs.decompiler.flash.exporters.modes.SoundExportMode; +import com.jpexs.decompiler.flash.exporters.modes.SpriteExportMode; +import com.jpexs.decompiler.flash.exporters.modes.TextExportMode; +import com.jpexs.decompiler.flash.exporters.settings.BinaryDataExportSettings; +import com.jpexs.decompiler.flash.exporters.settings.ButtonExportSettings; +import com.jpexs.decompiler.flash.exporters.settings.FontExportSettings; +import com.jpexs.decompiler.flash.exporters.settings.FrameExportSettings; +import com.jpexs.decompiler.flash.exporters.settings.ImageExportSettings; +import com.jpexs.decompiler.flash.exporters.settings.MorphShapeExportSettings; +import com.jpexs.decompiler.flash.exporters.settings.MovieExportSettings; +import com.jpexs.decompiler.flash.exporters.settings.ScriptExportSettings; +import com.jpexs.decompiler.flash.exporters.settings.ShapeExportSettings; +import com.jpexs.decompiler.flash.exporters.settings.SoundExportSettings; +import com.jpexs.decompiler.flash.exporters.settings.SpriteExportSettings; +import com.jpexs.decompiler.flash.exporters.settings.TextExportSettings; +import com.jpexs.decompiler.flash.exporters.swf.SwfXmlExporter; +import com.jpexs.decompiler.flash.gui.Main; +import com.jpexs.decompiler.flash.gui.helpers.CheckResources; +import com.jpexs.decompiler.flash.helpers.FileTextWriter; +import com.jpexs.decompiler.flash.importers.BinaryDataImporter; +import com.jpexs.decompiler.flash.importers.ImageImporter; +import com.jpexs.decompiler.flash.importers.ShapeImporter; +import com.jpexs.decompiler.flash.importers.SwfXmlImporter; +import com.jpexs.decompiler.flash.tags.DefineBinaryDataTag; +import com.jpexs.decompiler.flash.tags.DefineBitsJPEG2Tag; +import com.jpexs.decompiler.flash.tags.DefineBitsJPEG3Tag; +import com.jpexs.decompiler.flash.tags.DefineSpriteTag; +import com.jpexs.decompiler.flash.tags.JPEGTablesTag; +import com.jpexs.decompiler.flash.tags.Tag; +import com.jpexs.decompiler.flash.tags.base.ASMSource; +import com.jpexs.decompiler.flash.tags.base.ButtonTag; +import com.jpexs.decompiler.flash.tags.base.CharacterIdTag; +import com.jpexs.decompiler.flash.tags.base.CharacterTag; +import com.jpexs.decompiler.flash.tags.base.ImageTag; +import com.jpexs.decompiler.flash.tags.base.ShapeTag; +import com.jpexs.decompiler.flash.tags.base.SoundTag; +import com.jpexs.decompiler.flash.treeitems.SWFList; +import com.jpexs.decompiler.flash.types.ColorTransform; +import com.jpexs.decompiler.flash.types.RECT; +import com.jpexs.decompiler.flash.types.sound.SoundFormat; +import com.jpexs.decompiler.flash.xfl.FLAVersion; +import com.jpexs.decompiler.graph.CompilationException; +import com.jpexs.helpers.Helper; +import com.jpexs.helpers.Path; +import com.jpexs.helpers.streams.SeekableInputStream; +import com.sun.jna.Platform; +import com.sun.jna.platform.win32.Kernel32; +import gnu.jpdf.PDFJob; +import java.awt.Color; +import java.awt.Graphics; +import java.awt.image.BufferedImage; +import java.awt.print.PageFormat; +import java.awt.print.Paper; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Stack; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author JPEXS + */ +public class CommandLineArgumentParser { + + private static final Logger logger = Logger.getLogger(CommandLineArgumentParser.class.getName()); + + private static boolean commandLineMode = false; + + private static String stdOut = null; + + private static String stdErr = null; + + @SuppressWarnings("unchecked") + private static final ConfigurationItem[] commandlineConfigBoolean = new ConfigurationItem[]{ + Configuration.decompile, + Configuration.parallelSpeedUp, + Configuration.internalFlashViewer, + //Configuration.autoDeobfuscate, + Configuration.cacheOnDisk + }; + + public static boolean isCommandLineMode() { + return commandLineMode; + } + + public static void printCmdLineUsage() { + printCmdLineUsage(System.out, true); + } + + public static void printCmdLineUsage(PrintStream out, boolean printConfigs) { + int cnt = 1; + out.println("Commandline arguments:"); + out.println(" " + (cnt++) + ") -help | --help | /?"); + out.println(" ...shows commandline arguments (this help)"); + out.println(" " + (cnt++) + ") [ ...]"); + out.println(" ...opens SWF file(s) with the decompiler GUI"); + out.println(" " + (cnt++) + ") -proxy [-P]"); + out.println(" ...auto start proxy in the tray. Optional parameter -P specifies port for proxy. Defaults to 55555. "); + out.println(" " + (cnt++) + ") -export [-selectas3class ...]"); + out.println(" ...export sources to ."); + out.println(" Exports all files from when it is a folder."); + out.println(" Values for parameter:"); + out.println(" script - Scripts (Default format: ActionScript source)"); + out.println(" - Optional DEPRECATED \"-selectas3class\" parameter can be passed in same way as -selectclass"); + out.println(" image - Images (Default format: PNG/JPEG)"); + out.println(" shape - Shapes (Default format: SVG)"); + out.println(" morphshape - MorphShapes (Default format: SVG)"); + out.println(" movie - Movies (Default format: FLV without sound)"); + out.println(" font - Fonts (Default format: TTF)"); + out.println(" frame - Frames (Default format: PNG)"); + out.println(" sprite - Sprites (Default format: PNG)"); + out.println(" button - Buttons (Default format: PNG)"); + out.println(" sound - Sounds (Default format: MP3/WAV/FLV only sound)"); + out.println(" binaryData - Binary data (Default format: Raw data)"); + out.println(" text - Texts (Default format: Plain text)"); + out.println(" all - Every resource (but not FLA and XFL)"); + out.println(" fla - Everything to FLA compressed format"); + out.println(" xfl - Everything to uncompressed FLA format (XFL)"); + out.println(" You can export multiple types of items by using colon \",\""); + out.println(" DO NOT PUT space between comma (,) and next value."); + out.println(); + out.println(" Old DEPRECATED aliases include: (please use basic itemtypes and -format parameter instead)"); + out.println(" as, pcode, pcodehex, hex, all_as, all_pcode, all_pcodehex, textplain"); + out.println(); + out.println(" " + (cnt++) + ") -format "); + out.println(" ...sets output formats for export"); + out.println(" Values for parameter:"); + out.println(" script:as - ActionScript source"); + out.println(" script:pcode - ActionScript P-code"); + out.println(" script:pcodehex - ActionScript P-code with hex"); + out.println(" script:hex - ActionScript Hex only"); + out.println(" shape:svg - SVG format for Shapes"); + out.println(" shape:png - PNG format for Shapes"); + out.println(" shape:canvas - HTML5 Canvas format for Shapes"); + out.println(" shape:bmp - BMP format for Shapes"); + out.println(" morphshape:svg - SVG format for MorphShapes"); + out.println(" morphshape:canvas - HTML5 Canvas format for MorphShapes"); + out.println(" frame:png - PNG format for Frames"); + out.println(" frame:gif - GIF format for Frames"); + out.println(" frame:avi - AVI format for Frames"); + out.println(" frame:svg - SVG format for Frames"); + out.println(" frame:canvas - HTML5 Canvas format for Frames"); + out.println(" frame:pdf - PDF format for Frames"); + out.println(" frame:bmp - BMP format for Frames"); + out.println(" sprite:png - PNG format for Sprites"); + out.println(" sprite:gif - GIF format for Sprites"); + out.println(" sprite:avi - AVI format for Sprites"); + out.println(" sprite:svg - SVG format for Sprites"); + out.println(" sprite:canvas - HTML5 Canvas format for Sprites"); + out.println(" sprite:pdf - PDF format for Sprites"); + out.println(" sprite:bmp - BMP format for Sprites"); + out.println(" button:png - PNG format for Buttons"); + out.println(" button:svg - SVG format for Buttons"); + out.println(" button:bmp - BMP format for Buttons"); + out.println(" image:png_gif_jpeg - PNG/GIF/JPEG format for Images"); + out.println(" image:png - PNG format for Images"); + out.println(" image:jpeg - JPEG format for Images"); + out.println(" image:bmp - BMP format for Images"); + out.println(" text:plain - Plain text format for Texts"); + out.println(" text:formatted - Formatted text format for Texts"); + out.println(" text:svg - SVG format for Texts"); + out.println(" sound:mp3_wav_flv - MP3/WAV/FLV format for Sounds"); + out.println(" sound:mp3_wav - MP3/WAV format for Sounds"); + out.println(" sound:wav - WAV format for Sounds"); + out.println(" sound:flv - FLV format for Sounds"); + out.println(" font:ttf - TTF format for Fonts"); + out.println(" font:woff - WOFF format for Fonts"); + out.println(" fla: or xfl: - Specify FLA format version"); + out.println(" - values for : cs5,cs5.5,cs6,cc"); + + out.println(" You can set multiple formats at once using comma (,)"); + out.println(" DO NOT PUT space between comma (,) and next value."); + out.println(" The prefix with colon (:) is neccessary."); + out.println(" " + (cnt++) + ") -select "); + out.println(" ...selects frames/pages for export"); + out.println(" Example formats:"); + out.println(" 1-5"); + out.println(" 2,3"); + out.println(" 2-5,7,9-"); + out.println(" DO NOT PUT space between comma (,) and next ramge."); + out.println(" " + (cnt++) + ") -selectid "); + out.println(" ...selects characters for export by character id"); + out.println(" format is same as in -select"); + out.println(" " + (cnt++) + ") -selectclass "); + out.println(" ...selects scripts to export by class name (ActionScript 3 ONLY)"); + out.println(" format:"); + out.println(" com.example.MyClass"); + out.println(" com.example.+ (all classes in package \"com.example\")"); + out.println(" com.++,net.company.MyClass (all classes in package \"com\" and all subpackages, class net.company.MyClass)"); + out.println(" DO NOT PUT space between comma (,) and next class."); + out.println(" " + (cnt++) + ") -dumpSWF "); + out.println(" ...dumps list of SWF tags to console"); + out.println(" " + (cnt++) + ") -dumpAS2 "); + out.println(" ...dumps list of AS1/2 sctipts to console"); + out.println(" " + (cnt++) + ") -dumpAS3 "); + out.println(" ...dumps list of AS3 sctipts to console"); + out.println(" " + (cnt++) + ") -compress [(zlib|lzma)]"); + out.println(" ...Compress SWF and save it to . If is already compressed, it will be re-compressed. Default compression method is ZLIB"); + out.println(" " + (cnt++) + ") -decompress "); + out.println(" ...Decompress and save it to "); + out.println(" " + (cnt++) + ") -swf2xml "); + out.println(" ...Converts the SWF to XML file"); + out.println(" " + (cnt++) + ") -xml2swf "); + out.println(" ...Converts the XML to SWF file"); + out.println(" " + (cnt++) + ") -extract [-o |] [nocheck] [(all|biggest|smallest|first|last)]"); + out.println(" ...Extracts SWF files from ZIP or other binary files"); + out.println(" ...-o parameter should contain a file path when \"biggest\" or \"first\" parameter is specified"); + out.println(" ...-o parameter should contain a folder path when no exctaction mode or \"all\" parameter is specified"); + out.println(" " + (cnt++) + ") -renameInvalidIdentifiers (typeNumber|randomWord) "); + out.println(" ...Renames the invalid identifiers in and save it to "); + out.println(" " + (cnt++) + ") -config key=value[,key2=value2][,key3=value3...] [other parameters]"); + out.print(" ...Sets configuration values. "); + if (printConfigs) { + out.print("Available keys[current setting]:"); + for (ConfigurationItem item : commandlineConfigBoolean) { + out.print(" " + item + "[" + item.get() + "]"); + } + } + out.println(""); + out.println(" Values are boolean, you can use 0/1, true/false, on/off or yes/no."); + out.println(" If no other parameters passed, configuration is saved. Otherwise it is used only once."); + out.println(" DO NOT PUT space between comma (,) and next value."); + out.println(" " + (cnt++) + ") -onerror (abort|retryN|ignore)"); + out.println(" ...error handling mode. \"abort\" stops the exporting, \"retry\" tries the exporting N times, \"ignore\" ignores the current file"); + out.println(" " + (cnt++) + ") -timeout "); + out.println(" ...decompilation timeout for a single method in AS3 or single action in AS1/2 in seconds"); + out.println(" " + (cnt++) + ") -exportTimeout "); + out.println(" ...total export timeout in seconds"); + out.println(" " + (cnt++) + ") -exportFileTimeout "); + out.println(" ...export timeout for a single AS3 class in seconds"); + out.println(" " + (cnt++) + ") -flashpaper2pdf "); + out.println(" ...converts FlashPaper SWF file to PDF . Use -zoom parameter to specify image quality."); + out.println(" " + (cnt++) + ") -zoom "); + out.println(" ...apply zoom during export (currently for FlashPaper conversion only)"); + out.println(" " + (cnt++) + ") -replace (|) [methodBodyIndex1] [(|) [methodBodyIndex2]]..."); + out.println(" ...replaces the data of the specified BinaryData, Image, DefineSound tag or Script"); + out.println(" ...methodBodyIndexN parameter should be specified if and only if the imported entity is an AS3 P-Code"); + printCmdLineUsageExamples(out); + } + + private static void printCmdLineUsageExamples(PrintStream out) { + out.println(); + out.println("Examples:"); + out.println("java -jar ffdec.jar myfile.swf"); + out.println("java -jar ffdec.jar -proxy"); + out.println("java -jar ffdec.jar -proxy -P1234"); + out.println("java -jar ffdec.jar -export script \"C:\\decompiled\" myfile.swf"); + out.println("java -jar ffdec.jar -selectclass com.example.MyClass,com.example.SecondClass -export script \"C:\\decompiled\" myfile.swf"); + out.println("java -jar ffdec.jar -format script:pcode -export script \"C:\\decompiled\" myfile.swf"); + out.println("java -jar ffdec.jar -format script:pcode,text:plain -export script,text,image \"C:\\decompiled\" myfile.swf"); + out.println("java -jar ffdec.jar -format fla:cs5.5 -export fla \"C:\\sources\\myfile.fla\" myfile.swf"); + out.println("java -jar ffdec.jar -dumpSWF myfile.swf"); + out.println("java -jar ffdec.jar -compress myfile.swf myfiledec.swf"); + out.println("java -jar ffdec.jar -decompress myfiledec.swf myfile.swf"); + out.println("java -jar ffdec.jar -onerror ignore -export script \"C:\\decompiled\" myfile.swf"); + out.println("java -jar ffdec.jar -onerror retry 5 -export script \"C:\\decompiled\" myfile.swf"); + out.println("java -jar ffdec.jar -config autoDeobfuscate=1,parallelSpeedUp=0 -export script \"C:\\decompiled\" myfile.swf"); + out.println(""); + out.println("Instead of \"java -jar ffdec.jar\" you can use ffdec.bat on Windows, ffdec.sh on Linux/MacOs"); + } + + /** + * Parses the console arguments + * + * @param arguments Arguments + * @return paths to the file which should be opened or null + * @throws java.io.IOException On error + */ + public static String[] parseArguments(String[] arguments) throws IOException { + Level traceLevel = Level.WARNING; + Stack args = new Stack<>(); + for (int i = arguments.length - 1; i >= 0; i--) { + String arg = arguments[i]; + if (arg.length() > 0) { + args.add(arg); + } + } + + AbortRetryIgnoreHandler handler = null; + Map format = new HashMap<>(); + double zoom = 1; + Selection selection = new Selection(); + Selection selectionIds = new Selection(); + List selectionClasses = null; + String nextParam = null, nextParamOriginal = null; + OUTER: + while (true) { + nextParamOriginal = args.pop(); + if (nextParamOriginal != null) { + nextParam = nextParamOriginal.toLowerCase(); + } + switch (nextParam) { + case "-selectid": + selectionIds = parseSelect(args); + break; + case "-select": + selection = parseSelect(args); + break; + case "-selectclass": + selectionClasses = parseSelectClass(args); + break; + case "-zoom": + zoom = parseZoom(args); + break; + case "-format": + format = parseFormat(args); + break; + case "-config": + parseConfig(args); + if (args.isEmpty()) { + Configuration.saveConfig(); + System.out.println("Configuration saved"); + return null; + } + break; + case "-onerror": + handler = parseOnError(args); + break; + case "-timeout": + parseTimeout(args); + break; + case "-exporttimeout": + parseExportTimeout(args); + break; + case "-exportfiletimeout": + parseExportFileTimeout(args); + break; + case "-stdout": + parseStdOut(args); + break; + case "-stderr": + parseStdErr(args); + break; + case "-affinity": + parseAffinity(args); + break; + case "-priority": + parsePriority(args); + break; + case "-verbose": + traceLevel = Level.FINE; + break; + case "-debug": + for (int i = 0; i < arguments.length; i++) { + System.out.println(i + ".:" + arguments[i]); + } + Configuration.debugMode.set(true); + break; + case "--debugtool": + parseDebugTool(args); + break; + case "--compareresources": + parseCompareResources(args); + break; + default: + break OUTER; + } + if (args.isEmpty()) { + return null; + } + } + + String command = ""; + if (nextParam.startsWith("-")) { + command = nextParam.substring(1); + } + + if (command.equals("removefromcontextmenu")) { + if (!args.isEmpty()) { + badArguments(command); + } + ContextMenuTools.addToContextMenu(false, true); + System.exit(0); + } else if (command.equals("addtocontextmenu")) { + if (!args.isEmpty()) { + badArguments(command); + } + ContextMenuTools.addToContextMenu(true, true); + System.exit(0); + } else if (command.equals("proxy")) { + parseProxy(args); + } else if (command.equals("export")) { + parseExport(selectionClasses, selection, selectionIds, args, handler, traceLevel, format, zoom); + } else if (command.equals("compress")) { + parseCompress(args); + } else if (command.equals("decompress")) { + parseDecompress(args); + } else if (command.equals("swf2xml")) { + parseSwf2Xml(args); + } else if (command.equals("xml2swf")) { + parseXml2Swf(args); + } else if (command.equals("extract")) { + parseExtract(args); + } else if (command.equals("renameinvalididentifiers")) { + parseRenameInvalidIdentifiers(args); + } else if (command.equals("dumpswf")) { + parseDumpSwf(args); + } else if (command.equals("dumpas2")) { + parseDumpAS2(args); + } else if (command.equals("dumpas3")) { + parseDumpAS3(args); + } else if (command.equals("flashpaper2pdf")) { + parseFlashPaperToPdf(selection, zoom, args); + } else if (command.equals("replace")) { + parseReplace(args); + } else if (command.equals("as3compiler")) { + ActionScript3Parser.compile(null /*?*/, args.pop(), args.pop(), 0); + } else if (nextParam.equals("-help") || nextParam.equals("--help") || nextParam.equals("/?") || nextParam.equals("\\_") /* /? translates as this on windows */) { + printHeader(); + printCmdLineUsage(); + System.exit(0); + } else if (nextParam.equals("--webhelp")) { //for generating commandline usage on webpages + ByteArrayOutputStream whbaos = new ByteArrayOutputStream(); + printCmdLineUsage(new PrintStream(whbaos, true), false); + String wh = new String(whbaos.toByteArray()); + wh = wh.replace("<", "<").replace(">", ">"); + System.out.println(wh); + } else { + args.push(nextParamOriginal); // file names should be the original one + String[] fileNames = args.toArray(new String[args.size()]); + boolean allParamIsAFile = true; + for (String fileName : fileNames) { + File file = new File(fileName); + if (!file.exists() || !file.isFile()) { + allParamIsAFile = false; + } + } + + if (allParamIsAFile) { + return fileNames; + } else { + badArguments(); + } + } + + return null; + } + + public static void printHeader() { + System.out.println(ApplicationInfo.applicationVerName); + for (int i = 0; i < ApplicationInfo.applicationVerName.length(); i++) { + System.out.print("-"); + } + System.out.println(); + } + + public static void badArguments() { + badArguments(null); + } + + public static void badArguments(String command) { + System.err.println("Error: Bad Commandline Arguments!"); + printCmdLineUsage(); + System.exit(1); + } + + private static void setConfigurations(String cfgStr) { + String[] cfgs; + if (cfgStr.contains(",")) { + cfgs = cfgStr.split(","); + } else { + cfgs = new String[]{cfgStr}; + } + + for (String c : cfgs) { + String[] cp; + if (c.contains("=")) { + cp = c.split("="); + } else { + cp = new String[]{c, "1"}; + } + String key = cp[0]; + String value = cp[1]; + if (key.toLowerCase().equals("paralelSpeedUp".toLowerCase())) { + key = "parallelSpeedUp"; + } + for (ConfigurationItem item : commandlineConfigBoolean) { + if (key.toLowerCase().equals(item.getName().toLowerCase())) { + Boolean bValue = parseBooleanConfigValue(value); + if (bValue != null) { + System.out.println("Config " + item.getName() + " set to " + bValue); + item.set(bValue); + } + } + } + } + } + + private static Boolean parseBooleanConfigValue(String value) { + if (value == null) { + return null; + } + + Boolean bValue = null; + value = value.toLowerCase(); + if (value.equals("0") || value.equals("false") || value.equals("no") || value.equals("off")) { + bValue = false; + } + if (value.equals("1") || value.equals("true") || value.equals("yes") || value.equals("on")) { + bValue = true; + } + return bValue; + } + + private static void parseConfig(Stack args) { + if (args.isEmpty()) { + System.err.println("Config values expected"); + badArguments(); + } + setConfigurations(args.pop()); + } + + private static class Range { + + public Integer min; + + public Integer max; + + public Range(Integer min, Integer max) { + this.min = min; + this.max = max; + } + + public boolean contains(int index) { + int minimum = min == null ? Integer.MIN_VALUE : min; + int maximum = max == null ? Integer.MAX_VALUE : max; + + return index >= minimum && index <= maximum; + } + } + + private static class Selection { + + public List ranges; + + public Selection() { + this.ranges = new ArrayList<>(); + this.ranges.add(new Range(null, null)); + } + + public Selection(List ranges) { + this.ranges = ranges; + } + + public boolean contains(int index) { + for (Range r : ranges) { + if (r.contains(index)) { + return true; + } + } + return false; + } + } + + private static Selection parseSelect(Stack args) { + List ret = new ArrayList<>(); + if (args.isEmpty()) { + System.err.println("range parameter expected"); + badArguments(); + } + String range = args.pop(); + String[] ranges; + if (range.contains(",")) { + ranges = range.split(","); + } else { + ranges = new String[]{range}; + } + for (String r : ranges) { + Integer min = null; + Integer max = null; + if (r.contains("-")) { + String ps[] = r.split("\\-"); + if (ps.length != 2) { + System.err.println("invalid range"); + badArguments(); + } + try { + if (!"".equals(ps[0])) { + min = Integer.parseInt(ps[0]); + } + if (!"".equals(ps[1])) { + max = Integer.parseInt(ps[1]); + } + } catch (NumberFormatException nfe) { + System.err.println("invalid range"); + badArguments(); + } + } else { + try { + min = Integer.parseInt(r); + max = min; + } catch (NumberFormatException nfe) { + System.err.println("invalid range"); + badArguments(); + } + } + ret.add(new Range(min, max)); + } + return new Selection(ret); + } + + private static double parseZoom(Stack args) { + if (args.isEmpty()) { + System.err.println("zoom parameter expected"); + badArguments(); + } + try { + return Double.parseDouble(args.pop()); + } catch (NumberFormatException nfe) { + System.err.println("invalid zoom"); + badArguments(); + } + return 1; + } + + private static AbortRetryIgnoreHandler parseOnError(Stack args) { + int errorMode = AbortRetryIgnoreHandler.UNDEFINED; + int retryCount = 0; + + if (args.isEmpty()) { + System.err.println("onerror parameter expected"); + badArguments(); + } + String errorModeParameter = args.pop(); + switch (errorModeParameter) { + case "abort": + errorMode = AbortRetryIgnoreHandler.ABORT; + break; + case "retry": + errorMode = AbortRetryIgnoreHandler.RETRY; + if (args.isEmpty()) { + System.err.println("onerror retry count parameter expected"); + badArguments(); + } + + try { + retryCount = Integer.parseInt(args.pop()); + } catch (NumberFormatException nex) { + System.err.println("Bad retry count number"); + } + break; + case "ignore": + errorMode = AbortRetryIgnoreHandler.IGNORE; + break; + } + + return new ConsoleAbortRetryIgnoreHandler(errorMode, retryCount); + } + + private static void parseTimeout(Stack args) { + if (args.isEmpty()) { + System.err.println("timeout parameter expected"); + badArguments(); + } + try { + int timeout = Integer.parseInt(args.pop()); + Configuration.decompilationTimeoutSingleMethod.set(timeout); + } catch (NumberFormatException nex) { + System.err.println("Bad timeout value"); + } + } + + private static void parseExportTimeout(Stack args) { + if (args.isEmpty()) { + System.err.println("timeout parameter expected"); + badArguments(); + } + try { + int timeout = Integer.parseInt(args.pop()); + Configuration.exportTimeout.set(timeout); + } catch (NumberFormatException nex) { + System.err.println("Bad timeout value"); + } + } + + private static void parseExportFileTimeout(Stack args) { + if (args.isEmpty()) { + System.err.println("timeout parameter expected"); + badArguments(); + } + try { + int timeout = Integer.parseInt(args.pop()); + Configuration.decompilationTimeoutFile.set(timeout); + } catch (NumberFormatException nex) { + System.err.println("Bad timeout value"); + } + } + + private static void parseStdOut(Stack args) { + if (args.isEmpty()) { + System.err.println("stdOut parameter expected"); + badArguments(); + } + + stdOut = args.pop(); + } + + private static void parseStdErr(Stack args) { + if (args.isEmpty()) { + System.err.println("stdErr parameter expected"); + badArguments(); + } + + stdErr = args.pop(); + } + + private static void parseAffinity(Stack args) { + if (Platform.isWindows()) { + if (args.isEmpty()) { + System.err.println("affinity parameter expected"); + badArguments(); + } + try { + int affinityMask = Integer.parseInt(args.pop()); + Kernel32.INSTANCE.SetProcessAffinityMask(Kernel32.INSTANCE.GetCurrentProcess(), affinityMask); + } catch (NumberFormatException nex) { + System.err.println("Bad affinityMask value"); + } + } else { + System.err.println("Process affinity setting is only available on Windows platform."); + } + } + + private static void parsePriority(Stack args) { + if (Platform.isWindows()) { + if (args.isEmpty()) { + System.err.println("priority parameter expected"); + badArguments(); + } + String priority = args.pop(); + int priorityClass = 0; + switch (priority) { + case "low": + priorityClass = Kernel32.IDLE_PRIORITY_CLASS; + break; + case "belownormal": + priorityClass = Kernel32.BELOW_NORMAL_PRIORITY_CLASS; + break; + case "normal": + priorityClass = Kernel32.NORMAL_PRIORITY_CLASS; + break; + case "abovenormal": + priorityClass = Kernel32.ABOVE_NORMAL_PRIORITY_CLASS; + break; + case "high": + priorityClass = Kernel32.HIGH_PRIORITY_CLASS; + break; + case "realtime": + priorityClass = Kernel32.REALTIME_PRIORITY_CLASS; + break; + default: + System.err.println("Bad affinityMask value"); + } + if (priorityClass != 0) { + Kernel32.INSTANCE.SetPriorityClass(Kernel32.INSTANCE.GetCurrentProcess(), priorityClass); + } + } else { + System.err.println("Process priority setting is only available on Windows platform."); + } + } + + private static void parseDebugTool(Stack args) { + String cmd = args.pop().toLowerCase(); + switch (cmd) { + case "findtag": { + String folder = args.pop(); + String tagIdOrName = args.pop(); + int tagId; + try { + tagId = Integer.parseInt(tagIdOrName); + } catch (NumberFormatException e) { + tagId = Tag.getKnownClassesByName().get(tagIdOrName).getId(); + } + + PrintStream oldOut = System.out; + PrintStream oldErr = System.err; + PrintStream nullStream = new PrintStream(new OutputStream() { + @Override + public void write(int b) { + } + }); + System.setOut(nullStream); + System.setErr(nullStream); + Main.initLogging(Configuration.debugMode.get()); + + File[] files = new File(folder).listFiles(getSwfFilter()); + for (File file : files) { + SWFSourceInfo sourceInfo = new SWFSourceInfo(null, file.getAbsolutePath(), file.getName()); + try { + SWF swf = new SWF(new FileInputStream(file), sourceInfo.getFile(), sourceInfo.getFileTitle(), Configuration.parallelSpeedUp.get()); + swf.swfList = new SWFList(); + swf.swfList.sourceInfo = sourceInfo; + boolean found = false; + for (Tag tag : swf.tags) { + if (tag.getId() == tagId) { + found = true; + break; + } + } + + if (found) { + oldOut.println("Tag found in file: " + file.getAbsolutePath()); + } + } catch (IOException | InterruptedException ex) { + logger.log(Level.SEVERE, null, ex); + } + } + + System.setOut(oldOut); + System.setErr(oldErr); + Main.initLogging(Configuration.debugMode.get()); + break; + } + case "finderrorheader": { + String folder = args.pop(); + + PrintStream oldOut = System.out; + PrintStream oldErr = System.err; + PrintStream nullStream = new PrintStream(new OutputStream() { + @Override + public void write(int b) { + } + }); + System.setOut(nullStream); + System.setErr(nullStream); + Main.initLogging(Configuration.debugMode.get()); + + File[] files = new File(folder).listFiles(getSwfFilter()); + for (File file : files) { + SWFSourceInfo sourceInfo = new SWFSourceInfo(null, file.getAbsolutePath(), file.getName()); + try { + SWF swf = new SWF(new FileInputStream(file), sourceInfo.getFile(), sourceInfo.getFileTitle(), Configuration.parallelSpeedUp.get()); + swf.swfList = new SWFList(); + swf.swfList.sourceInfo = sourceInfo; + boolean found = false; + for (Tag tag : swf.tags) { + if (tag instanceof JPEGTablesTag) { + JPEGTablesTag jtt = (JPEGTablesTag) tag; + if (ImageTag.hasErrorHeader(jtt.jpegData)) { + found = true; + break; + } + } else if (tag instanceof DefineBitsJPEG2Tag) { + DefineBitsJPEG2Tag jpeg = (DefineBitsJPEG2Tag) tag; + if (ImageTag.hasErrorHeader(jpeg.imageData)) { + found = true; + break; + } + } else if (tag instanceof DefineBitsJPEG3Tag) { + DefineBitsJPEG3Tag jpeg = (DefineBitsJPEG3Tag) tag; + if (ImageTag.hasErrorHeader(jpeg.imageData)) { + found = true; + break; + } + } + } + + if (found) { + oldOut.println("Tag found in file: " + file.getAbsolutePath()); + } + } catch (IOException | InterruptedException ex) { + logger.log(Level.SEVERE, null, ex); + } + } + + System.setOut(oldOut); + System.setErr(oldErr); + Main.initLogging(Configuration.debugMode.get()); + break; + } + } + } + + private static void parseCompareResources(Stack args) { + String revision = args.pop().toLowerCase(); + String revision2 = null; + if (!args.isEmpty()) { + revision2 = args.pop(); + } + + CheckResources.compareResources(System.out, revision, revision2); + } + + private static void parseProxy(Stack args) { + int port = 55555; + String portStr = args.peek(); + if (portStr != null && portStr.startsWith("-P")) { + args.pop(); + try { + port = Integer.parseInt(portStr.substring(2)); + } catch (NumberFormatException nex) { + System.err.println("Bad port number"); + } + } + Main.startProxy(port); + } + + private static List parseSelectClassOld(Stack args) { + List ret = new ArrayList<>(); + if (!args.isEmpty() && args.peek().equals("-selectas3class")) { + args.pop(); + while (!args.isEmpty()) { + ret.add(args.pop()); + } + System.err.println("WARNING: Using deprecated -selectas3class parameter. Please use -selectclass instead. See --help for usage."); + } + return ret; + + } + + private static List parseSelectClass(Stack args) { + if (args.size() < 1) { + badArguments(); + } + List ret = new ArrayList<>(); + String classesStr = args.pop(); + String classes[]; + if (classesStr.contains(",")) { + classes = classesStr.split(","); + } else { + classes = new String[]{classesStr}; + } + ret.addAll(Arrays.asList(classes)); + return ret; + + } + + private static void parseExport(List selectionClasses, Selection selection, Selection selectionIds, Stack args, AbortRetryIgnoreHandler handler, Level traceLevel, Map formats, double zoom) { + if (args.size() < 3) { + badArguments(); + } + String[] validExportItems = new String[]{ + "script", + "image", + "shape", + "morphshape", + "movie", + "font", + "frame", + "sprite", + "button", + "sound", + "binarydata", + "text", + "all", + "fla", + "xfl" + }; + + String[] removedExportFormats = new String[]{ + "as", + "pcode", + "hex", + "pcodehex", + "all_as", + "all_pcode", + "all_pcodehex", + "all_hex", + "textplain" + }; + + if (handler == null) { + handler = new ConsoleAbortRetryIgnoreHandler(AbortRetryIgnoreHandler.UNDEFINED, 0); + } + String exportFormatString = args.pop().toLowerCase(); + List exportFormats = Arrays.asList(exportFormatString.split(",")); + long startTime = System.currentTimeMillis(); + + File outDirBase = new File(args.pop()); + File inFileOrFolder = new File(args.pop()); + if (!inFileOrFolder.exists()) { + System.err.println("Input SWF file does not exist!"); + badArguments(); + } + printHeader(); + boolean exportOK = true; + + List as3classes = new ArrayList<>(); + if (selectionClasses != null) { + as3classes.addAll(selectionClasses); + } + + try { + File[] inFiles; + boolean singleFile = true; + if (inFileOrFolder.isDirectory()) { + singleFile = false; + inFiles = inFileOrFolder.listFiles(getSwfFilter()); + } else { + inFiles = new File[]{inFileOrFolder}; + } + + for (File inFile : inFiles) { + String inFileName = Path.getFileNameWithoutExtension(inFile); + if (stdOut != null) { + System.setOut(new PrintStream(new FileOutputStream(stdOut.replace("{swfFile}", inFileName), true))); + } + + if (stdErr != null) { + System.setErr(new PrintStream(new FileOutputStream(stdErr.replace("{swfFile}", inFileName), true))); + Main.initLogging(Configuration.debugMode.get()); + } + + long startTimeSwf = 0; + if (!singleFile) { + startTimeSwf = System.currentTimeMillis(); + System.out.println("Start exporting " + inFile.getName()); + } + + SWFSourceInfo sourceInfo = new SWFSourceInfo(null, inFile.getAbsolutePath(), inFile.getName()); + SWF swf; + try { + swf = new SWF(new FileInputStream(inFile), sourceInfo.getFile(), sourceInfo.getFileTitle(), Configuration.parallelSpeedUp.get()); + } catch (SwfOpenException ex) { + logger.log(Level.SEVERE, "Failed to open swf: " + inFile.getName(), ex); + continue; + } + + swf.swfList = new SWFList(); + swf.swfList.sourceInfo = sourceInfo; + String outDir = outDirBase.getAbsolutePath(); + if (!singleFile) { + outDir = Path.combine(outDir, inFile.getName()); + } + + List extags = new ArrayList<>(); + for (Tag t : swf.tags) { + if (t instanceof CharacterIdTag) { + CharacterIdTag c = (CharacterIdTag) t; + if (selectionIds.contains(c.getCharacterId())) { + extags.add(t); + } + } else { + if (selectionIds.contains(0)) { + extags.add(t); + } + } + } + + final Level level = traceLevel; + swf.addEventListener(new EventListener() { + @Override + public void handleExportingEvent(String type, int index, int count, Object data) { + if (level.intValue() <= Level.FINE.intValue()) { + String text = "Exporting "; + if (type != null && type.length() > 0) { + text += type + " "; + } + System.out.println(text + index + "/" + count + " " + data); + } + } + + @Override + public void handleExportedEvent(String type, int index, int count, Object data) { + String text = "Exported "; + if (type != null && type.length() > 0) { + text += type + " "; + } + System.out.println(text + index + "/" + count + " " + data); + } + + @Override + public void handleEvent(String event, Object data) { + } + }); + + // First check all the specified export formats + for (String exportFormat : exportFormats) { + if (Arrays.asList(removedExportFormats).contains(exportFormat)) { + System.err.println("Error: Export format : " + exportFormat + " was REMOVED. Run application with --help parameter to see available formats."); + System.exit(1); + } else if (!Arrays.asList(validExportItems).contains(exportFormat)) { + System.err.println("Invalid export item:" + exportFormat); + badArguments(); + } + } + + // Here the exportFormats array should contain only validitems + commandLineMode = true; + boolean exportAll = exportFormats.contains("all"); + boolean multipleExportTypes = exportAll || exportFormats.size() > 1; + EventListener evl = swf.getExportEventListener(); + + if (exportAll || exportFormats.contains("image")) { + System.out.println("Exporting images..."); + new ImageExporter().exportImages(handler, outDir + (multipleExportTypes ? File.separator + ImageExportSettings.EXPORT_FOLDER_NAME : ""), extags, new ImageExportSettings(enumFromStr(formats.get("image"), ImageExportMode.class)), evl); + } + + if (exportAll || exportFormats.contains("shape")) { + System.out.println("Exporting shapes..."); + new ShapeExporter().exportShapes(handler, outDir + (multipleExportTypes ? File.separator + ShapeExportSettings.EXPORT_FOLDER_NAME : ""), extags, new ShapeExportSettings(enumFromStr(formats.get("shape"), ShapeExportMode.class), zoom), evl); + } + + if (exportAll || exportFormats.contains("morphshape")) { + System.out.println("Exporting morphshapes..."); + new MorphShapeExporter().exportMorphShapes(handler, outDir + (multipleExportTypes ? File.separator + MorphShapeExportSettings.EXPORT_FOLDER_NAME : ""), extags, new MorphShapeExportSettings(enumFromStr(formats.get("morphshape"), MorphShapeExportMode.class), zoom), evl); + } + + if (exportAll || exportFormats.contains("movie")) { + System.out.println("Exporting movies..."); + new MovieExporter().exportMovies(handler, outDir + (multipleExportTypes ? File.separator + MovieExportSettings.EXPORT_FOLDER_NAME : ""), extags, new MovieExportSettings(enumFromStr(formats.get("movie"), MovieExportMode.class)), evl); + } + + if (exportAll || exportFormats.contains("font")) { + System.out.println("Exporting fonts..."); + new FontExporter().exportFonts(handler, outDir + (multipleExportTypes ? File.separator + FontExportSettings.EXPORT_FOLDER_NAME : ""), extags, new FontExportSettings(enumFromStr(formats.get("font"), FontExportMode.class)), evl); + } + + if (exportAll || exportFormats.contains("sound")) { + System.out.println("Exporting sounds..."); + new SoundExporter().exportSounds(handler, outDir + (multipleExportTypes ? File.separator + SoundExportSettings.EXPORT_FOLDER_NAME : ""), extags, new SoundExportSettings(enumFromStr(formats.get("sound"), SoundExportMode.class)), evl); + } + + if (exportAll || exportFormats.contains("binarydata")) { + System.out.println("Exporting binaryData..."); + new BinaryDataExporter().exportBinaryData(handler, outDir + (multipleExportTypes ? File.separator + BinaryDataExportSettings.EXPORT_FOLDER_NAME : ""), extags, new BinaryDataExportSettings(enumFromStr(formats.get("binarydata"), BinaryDataExportMode.class)), evl); + } + + if (exportAll || exportFormats.contains("text")) { + System.out.println("Exporting texts..."); + Boolean singleTextFile = parseBooleanConfigValue(formats.get("singletext")); + if (singleTextFile == null) { + singleTextFile = Configuration.textExportSingleFile.get(); + } + new TextExporter().exportTexts(handler, outDir + (multipleExportTypes ? File.separator + TextExportSettings.EXPORT_FOLDER_NAME : ""), extags, new TextExportSettings(enumFromStr(formats.get("text"), TextExportMode.class), singleTextFile, zoom), evl); + } + + FrameExporter frameExporter = new FrameExporter(); + + if (exportAll || exportFormats.contains("frame")) { + System.out.println("Exporting frames..."); + List frames = new ArrayList<>(); + for (int i = 0; i < swf.frameCount; i++) { + if (selection.contains(i + 1)) { + frames.add(i); + } + } + FrameExportSettings fes = new FrameExportSettings(enumFromStr(formats.get("frame"), FrameExportMode.class), zoom); + frameExporter.exportFrames(handler, outDir + (multipleExportTypes ? File.separator + FrameExportSettings.EXPORT_FOLDER_NAME : ""), swf, 0, frames, fes, evl); + } + + if (exportAll || exportFormats.contains("sprite")) { + System.out.println("Exporting sprite..."); + SpriteExportSettings ses = new SpriteExportSettings(enumFromStr(formats.get("sprite"), SpriteExportMode.class), zoom); + for (CharacterTag c : swf.getCharacters().values()) { + if (c instanceof DefineSpriteTag) { + frameExporter.exportFrames(handler, outDir + (multipleExportTypes ? File.separator + SpriteExportSettings.EXPORT_FOLDER_NAME : ""), swf, c.getCharacterId(), null, ses, evl); + } + } + } + + if (exportAll || exportFormats.contains("button")) { + System.out.println("Exporting buttons..."); + ButtonExportSettings bes = new ButtonExportSettings(enumFromStr(formats.get("button"), ButtonExportMode.class), zoom); + for (CharacterTag c : swf.getCharacters().values()) { + if (c instanceof ButtonTag) { + List frameNums = new ArrayList<>(); + frameNums.add(0); // todo: export all frames + frameExporter.exportFrames(handler, outDir + (multipleExportTypes ? File.separator + ButtonExportSettings.EXPORT_FOLDER_NAME : ""), swf, c.getCharacterId(), frameNums, bes, evl); + } + } + } + + boolean parallel = Configuration.parallelSpeedUp.get(); + Boolean singleScriptFile = parseBooleanConfigValue(formats.get("singlescript")); + if (singleScriptFile == null) { + singleScriptFile = Configuration.scriptExportSingleFile.get(); + } + + if (parallel && singleScriptFile) { + System.out.println("Single file script export is not supported with enabled parallel speedup"); + singleScriptFile = false; + } + + ScriptExportSettings scriptExportSettings = new ScriptExportSettings(enumFromStr(formats.get("script"), ScriptExportMode.class), singleScriptFile); + if (exportAll || exportFormats.contains("script")) { + System.out.println("Exporting scripts..."); + if (as3classes.isEmpty()) { + as3classes = parseSelectClassOld(args); + } + + String scriptsFolder = Path.combine(outDir, ScriptExportSettings.EXPORT_FOLDER_NAME); + Path.createDirectorySafe(new File(scriptsFolder)); + String singleFileName = Path.combine(scriptsFolder, swf.getShortFileName() + scriptExportSettings.getFileExtension()); + try (FileTextWriter writer = scriptExportSettings.singleFile ? new FileTextWriter(Configuration.getCodeFormatting(), new FileOutputStream(singleFileName)) : null) { + scriptExportSettings.singleFileWriter = writer; + if (!as3classes.isEmpty()) { + for (String as3class : as3classes) { + exportOK = exportOK && swf.exportAS3Class(as3class, scriptsFolder, scriptExportSettings, parallel, evl); + } + } else { + exportOK = exportOK && swf.exportActionScript(handler, scriptsFolder, scriptExportSettings, parallel, evl) != null; + } + } + } + + if (exportFormats.contains("fla")) { + System.out.println("Exporting FLA..."); + FLAVersion flaVersion = FLAVersion.fromString(formats.get("fla")); + if (flaVersion == null) { + flaVersion = FLAVersion.CS6; //Defaults to CS6 + } + swf.exportFla(handler, outDir + (multipleExportTypes ? File.separator + "fla" : ""), inFile.getName(), ApplicationInfo.APPLICATION_NAME, ApplicationInfo.applicationVerName, ApplicationInfo.version, Configuration.parallelSpeedUp.get(), flaVersion); + } + + if (exportFormats.contains("xfl")) { + System.out.println("Exporting XFL..."); + FLAVersion xflVersion = FLAVersion.fromString(formats.get("xfl")); + if (xflVersion == null) { + xflVersion = FLAVersion.CS6; //Defaults to CS6 + } + swf.exportXfl(handler, outDir + (multipleExportTypes ? File.separator + "xfl" : ""), inFile.getName(), ApplicationInfo.APPLICATION_NAME, ApplicationInfo.applicationVerName, ApplicationInfo.version, Configuration.parallelSpeedUp.get(), xflVersion); + } + + if (!singleFile) { + long stopTimeSwf = System.currentTimeMillis(); + long time = stopTimeSwf - startTimeSwf; + System.out.println("Export finished: " + inFile.getName() + " Export time: " + Helper.formatTimeSec(time)); + } + + swf.clearAllCache(); + } + } catch (OutOfMemoryError | Exception ex) { + System.err.print("FAIL: Exporting Failed on Exception - "); + logger.log(Level.SEVERE, null, ex); + System.exit(1); + } + + long stopTime = System.currentTimeMillis(); + long time = stopTime - startTime; + System.out.println("Export finished. Total export time: " + Helper.formatTimeSec(time)); + if (exportOK) { + System.out.println("OK"); + System.exit(0); + } else { + System.err.println("FAIL"); + System.exit(1); + } + } + + private static void parseCompress(Stack args) { + if (args.size() < 2) { + badArguments(); + } + + try { + try (InputStream fis = new BufferedInputStream(new FileInputStream(args.pop())); + OutputStream fos = new BufferedOutputStream(new FileOutputStream(args.pop()))) { + SWFCompression compression = SWFCompression.ZLIB; + String compressionString = !args.isEmpty() ? args.pop() : null; + if (compressionString != null) { + switch (compressionString.toLowerCase()) { + case "zlib": + compression = SWFCompression.ZLIB; + break; + case "lzma": + compression = SWFCompression.LZMA; + break; + default: + System.out.println("Unsupported compression method: " + compressionString); + System.exit(0); + break; + } + } + + if (SWF.compress(fis, fos, compression)) { + System.out.println("OK"); + } else { + System.err.println("FAIL"); + } + } catch (FileNotFoundException ex) { + System.err.println("File not found."); + } + } catch (IOException ex) { + logger.log(Level.SEVERE, null, ex); + } + + System.exit(0); + } + + private static void parseDecompress(Stack args) { + if (args.size() < 2) { + badArguments(); + } + + try { + try (InputStream fis = new BufferedInputStream(new FileInputStream(args.pop())); + OutputStream fos = new BufferedOutputStream(new FileOutputStream(args.pop()))) { + if (SWF.decompress(fis, fos)) { + System.out.println("OK"); + System.exit(0); + } else { + System.err.println("FAIL"); + System.exit(1); + } + } catch (FileNotFoundException ex) { + System.err.println("File not found."); + } + } catch (IOException ex) { + logger.log(Level.SEVERE, null, ex); + } + + System.exit(0); + } + + private static void parseSwf2Xml(Stack args) { + if (args.size() < 2) { + badArguments(); + } + + try { + try (FileInputStream is = new FileInputStream(args.pop())) { + SWF swf = new SWF(is, Configuration.parallelSpeedUp.get()); + new SwfXmlExporter().exportXml(swf, new File(args.pop())); + } catch (FileNotFoundException ex) { + System.err.println("File not found."); + } catch (InterruptedException ex) { + logger.log(Level.SEVERE, null, ex); + } + } catch (IOException ex) { + logger.log(Level.SEVERE, null, ex); + } + + System.exit(0); + } + + private static void parseXml2Swf(Stack args) { + if (args.size() < 2) { + badArguments(); + } + + try { + String xml = Helper.readTextFile(args.pop()); + SWF swf = new SWF(); + new SwfXmlImporter().importSwf(swf, xml); + try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(new File(args.pop())))) { + swf.saveTo(new BufferedOutputStream(fos)); + } + } catch (IOException ex) { + logger.log(Level.SEVERE, null, ex); + } + + System.exit(0); + } + + private static void parseExtract(Stack args) { + if (args.size() < 1) { + badArguments(); + } + + String fileName = args.pop(); + SearchMode mode = SearchMode.ALL; + + boolean noCheck = false; + String output = null; + + if (args.size() > 0 && args.peek().toLowerCase().equals("-o")) { + args.pop(); + if (args.size() < 1) { + badArguments(); + } + output = args.pop(); + } + + if (args.size() > 0 && args.peek().toLowerCase().equals("nocheck")) { + noCheck = true; + args.pop(); + } + + if (args.size() > 0) { + String modeStr = args.pop().toLowerCase(); + switch (modeStr) { + case "biggest": + mode = SearchMode.BIGGEST; + break; + case "smallest": + mode = SearchMode.SMALLEST; + break; + case "first": + mode = SearchMode.FIRST; + break; + case "last": + mode = SearchMode.LAST; + break; + } + } + + try { + SWFSourceInfo sourceInfo = new SWFSourceInfo(null, fileName, null); + if (!sourceInfo.isBundle()) { + System.err.println("Error: should be a bundle. (ZIP or non SWF binary file)"); + System.exit(1); + } + SWFBundle bundle = sourceInfo.getBundle(noCheck, mode); + List> streamsToExtract = new ArrayList<>(); + for (Map.Entry streamEntry : bundle.getAll().entrySet()) { + InputStream stream = streamEntry.getValue(); + stream.reset(); + streamsToExtract.add(streamEntry); + } + + for (Map.Entry streamEntry : streamsToExtract) { + InputStream stream = streamEntry.getValue(); + stream.reset(); + String fileNameOut; + if (mode != SearchMode.ALL) { + if (output == null) { + fileNameOut = Path.getFileNameWithoutExtension(new File(fileName)) + ".swf"; + } else { + fileNameOut = output; + } + } else { + fileNameOut = streamEntry.getKey() + ".swf"; + if (output != null) { + fileNameOut = Path.combine(output, fileNameOut); + } + } + + try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(fileNameOut))) { + byte[] swfData = new byte[stream.available()]; + int cnt = stream.read(swfData); + fos.write(swfData, 0, cnt); + } + } + } catch (IOException ex) { + logger.log(Level.SEVERE, null, ex); + } + + System.exit(0); + } + + private static void parseRenameInvalidIdentifiers(Stack args) { + if (args.size() < 3) { + badArguments(); + } + + String renameTypeStr = args.pop(); + RenameType renameType; + switch (renameTypeStr.toLowerCase()) { + case "typenumber": + renameType = RenameType.TYPENUMBER; + break; + case "randomword": + renameType = RenameType.RANDOMWORD; + break; + default: + System.err.println("Invalid rename type:" + renameTypeStr); + badArguments(); + return; + } + + try { + try (InputStream fis = new BufferedInputStream(new FileInputStream(args.pop())); + OutputStream fos = new BufferedOutputStream(new FileOutputStream(args.pop()))) { + if (SWF.renameInvalidIdentifiers(renameType, fis, fos)) { + System.out.println("OK"); + System.exit(0); + } else { + System.err.println("FAIL"); + System.exit(1); + } + } catch (FileNotFoundException ex) { + System.err.println("File not found."); + } + } catch (IOException ex) { + logger.log(Level.SEVERE, null, ex); + } + + System.exit(0); + } + + private static Map parseFormat(Stack args) { + if (args.size() < 1) { + badArguments(); + } + String fmtStr = args.pop(); + String[] fmts; + if (fmtStr.contains(",")) { + fmts = fmtStr.split(","); + } else { + fmts = new String[]{fmtStr}; + } + Map ret = new HashMap<>(); + for (String fmt : fmts) { + String[] parts = fmt.split(":"); + ret.put(parts[0].toLowerCase(), parts[1].toLowerCase()); + } + return ret; + } + + private static void parseFlashPaperToPdf(Selection selection, double zoom, Stack args) { + if (args.size() < 2) { + badArguments(); + } + File inFile = new File(args.pop()); + File outFile = new File(args.pop()); + printHeader(); + + try (FileInputStream is = new FileInputStream(inFile)) { + + PDFJob job = null; + + SWF swf = new SWF(is, Configuration.parallelSpeedUp.get()); + int totalPages = 0; + + for (Tag t : swf.tags) { + if (t instanceof DefineSpriteTag) { + DefineSpriteTag ds = (DefineSpriteTag) t; + if ("page1".equals(ds.getExportName())) { + totalPages = 1; + } else { + if (totalPages > 0) { + totalPages++; + } + } + } + } + + int page = 0; + + for (Tag t : swf.tags) { + if (t instanceof DefineSpriteTag) { + DefineSpriteTag ds = (DefineSpriteTag) t; + if ("page1".equals(ds.getExportName())) { + page = 1; + job = new PDFJob(new BufferedOutputStream(new FileOutputStream(outFile))); + } else { + if (page > 0) { + page++; + } + } + if (("page" + page).equals(ds.getExportName())) { + if (!selection.contains(page)) { + continue; + } + System.out.print("Page " + page + "/" + totalPages + "..."); + RECT displayRect = new RECT(ds.getTimeline().displayRect); + //displayRect.Xmax *= zoom; + //displayRect.Ymax *= zoom; + Matrix m = new Matrix(); + //m.scale(zoom); + BufferedImage img = SWF.frameToImageGet(ds.getTimeline(), 0, 0, null, 0, displayRect, m, new ColorTransform(), Color.white, false, zoom).getBufferedImage(); + PageFormat pf = new PageFormat(); + pf.setOrientation(PageFormat.PORTRAIT); + Paper p = new Paper(); + p.setSize(img.getWidth(), img.getHeight()); + pf.setPaper(p); + Graphics g = job.getGraphics(pf); + g.drawImage(img, 0, 0, img.getWidth(), img.getHeight(), null); + g.dispose(); + System.out.println("OK"); + + } + } + } + + if (job == null) { + System.err.println("No pages found. Maybe it is not a FlashPaper file"); + System.exit(2); + } + job.end(); + + } catch (FileNotFoundException ex) { + System.err.println("File not found"); + System.exit(1); + } catch (IOException | InterruptedException ex) { + System.err.println("I/O error during reading"); + System.exit(2); + } + System.exit(0); + } + + private static void parseReplace(Stack args) { + if (args.size() < 4) { + badArguments(); + } + + File inFile = new File(args.pop()); + File outFile = new File(args.pop()); + try { + try (FileInputStream is = new FileInputStream(inFile)) { + SWF swf = new SWF(is, Configuration.parallelSpeedUp.get()); + while (true) { + String objectToReplace = args.pop(); + + if (objectToReplace.matches("\\d+")) { + // replace character tag + int characterId = 0; + try { + characterId = Integer.parseInt(objectToReplace); + } catch (NumberFormatException nfe) { + System.err.println("CharacterId should be integer"); + System.exit(1); + } + if (!swf.getCharacters().containsKey(characterId)) { + System.err.println("CharacterId does not exist"); + System.exit(1); + } + + CharacterTag characterTag = swf.getCharacter(characterId); + String repFile = args.pop(); + byte[] data = Helper.readFile(repFile); + if (characterTag instanceof DefineBinaryDataTag) { + DefineBinaryDataTag defineBinaryData = (DefineBinaryDataTag) characterTag; + new BinaryDataImporter().importData(defineBinaryData, data); + } else if (characterTag instanceof ImageTag) { + ImageTag imageTag = (ImageTag) characterTag; + new ImageImporter().importImage(imageTag, data); + } else if (characterTag instanceof ShapeTag) { + ShapeTag shapeTag = (ShapeTag) characterTag; + new ShapeImporter().importImage(shapeTag, data); + } else if (characterTag instanceof SoundTag) { + SoundTag st = (SoundTag) characterTag; + int soundFormat = SoundFormat.FORMAT_UNCOMPRESSED_LITTLE_ENDIAN; + if (repFile.toLowerCase().endsWith(".mp3")) { + soundFormat = SoundFormat.FORMAT_MP3; + } + boolean ok = st.setSound(new ByteArrayInputStream(data), soundFormat); + if (!ok) { + System.err.println("Import FAILED. Maybe unsuppoted media type? Only MP3 and uncompressed WAV are available."); + System.exit(1); + } + } else { + System.err.println("The specified tag type is not supported for import"); + System.exit(1); + } + } else { + Map asms = swf.getASMs(false); + boolean found = false; + if (asms.containsKey(objectToReplace)) { + found = true; + // replace AS1/2 + String repFile = args.pop(); + String repText = Helper.readTextFile(repFile); + ASMSource src = asms.get(objectToReplace); + if (Path.getExtension(repFile).equals(".as")) { + replaceAS2(repText, src); + } else { + replaceAS2PCode(repText, src); + } + } else { + List packs = swf.getAS3Packs(); + for (ScriptPack entry : packs) { + if (entry.getClassPath().toString().equals(objectToReplace)) { + found = true; + // replace AS3 + String repFile = args.pop(); + String repText = Helper.readTextFile(repFile); + ScriptPack pack = entry; + if (Path.getExtension(repFile).equals(".as")) { + replaceAS3(repText, pack); + } else { + // todo: get traits + if (args.isEmpty()) { + badArguments(); + } + int bodyIndex = Integer.parseInt(args.pop()); + //int classIndex = 0; + //int traitId = 0; + Trait trait = null; //abc.findTraitByTraitId(classIndex, traitId); + replaceAS3PCode(repText, pack.abc, bodyIndex, trait); + } + } + } + } + + if (!found) { + System.err.println(objectToReplace + " is not reocginized as a CharacterId or a script name."); + System.exit(1); + } + } + + if (args.isEmpty() || args.peek().startsWith("-")) { + break; + } + } + + try { + try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(outFile))) { + swf.saveTo(fos); + } + } catch (IOException e) { + System.err.println("I/O error during writing"); + System.exit(2); + } + } + } catch (IOException | InterruptedException e) { + System.err.println("I/O error during reading"); + System.exit(2); + } + } + + private static void replaceAS2PCode(String text, ASMSource src) throws IOException, InterruptedException { + System.out.println("Replace AS1/2 PCode"); + if (text.trim().startsWith(Helper.hexData)) { + src.setActionBytes(Helper.getBytesFromHexaText(text)); + } else { + try { + src.setActions(ASMParser.parse(0, true, text, src.getSwf().version, false)); + } catch (ActionParseException ex) { + System.err.println("%error% on line %line%".replace("%error%", ex.text).replace("%line%", Long.toString(ex.line))); + System.exit(1); + } + } + src.setModified(); + } + + private static void replaceAS2(String as, ASMSource src) throws IOException, InterruptedException { + System.out.println("Replace AS1/2"); + System.out.println("Warning: This feature is EXPERIMENTAL"); + ActionScript2Parser par = new ActionScript2Parser(src.getSwf().version); + try { + src.setActions(par.actionsFromString(as)); + } catch (ActionParseException ex) { + System.err.println("%error% on line %line%".replace("%error%", ex.text).replace("%line%", Long.toString(ex.line))); + System.exit(1); + } catch (CompilationException ex) { + System.err.println("%error% on line %line%".replace("%error%", ex.text).replace("%line%", Long.toString(ex.line))); + System.exit(1); + } + src.setModified(); + } + + private static void replaceAS3PCode(String text, ABC abc, int bodyIndex, Trait trait) throws IOException, InterruptedException { + System.out.println("Replace AS3 PCode"); + if (text.trim().startsWith(Helper.hexData)) { + byte[] data = Helper.getBytesFromHexaText(text); + MethodBody mb = abc.bodies.get(bodyIndex); + mb.setCodeBytes(data); + } else { + try { + AVM2Code acode = ASM3Parser.parse(new StringReader(text), abc.constants, trait, new MissingSymbolHandler() { + //no longer ask for adding new constants + @Override + public boolean missingString(String value) { + return true; + } + + @Override + public boolean missingInt(long value) { + return true; + } + + @Override + public boolean missingUInt(long value) { + return true; + } + + @Override + public boolean missingDouble(double value) { + return true; + } + }, abc.bodies.get(bodyIndex), abc.method_info.get(abc.bodies.get(bodyIndex).method_info)); + //acode.getBytes(abc.bodies.get(bodyIndex).getCodeBytes()); + abc.bodies.get(bodyIndex).setCode(acode); + } catch (AVM2ParseException ex) { + System.err.println("%error% on line %line%".replace("%error%", ex.text).replace("%line%", Long.toString(ex.line))); + System.exit(1); + } + } + ((Tag) abc.parentTag).setModified(true); + } + + private static void replaceAS3(String as, ScriptPack pack) throws IOException, InterruptedException { + System.out.println("Replace AS3"); + System.out.println("Warning: This feature is EXPERIMENTAL"); + File swc = Configuration.getPlayerSWC(); + if (swc == null) { + final String adobePage = "http://www.adobe.com/support/flashplayer/downloads.html"; + System.err.println("For ActionScript 3 direct editation, a library called \"PlayerGlobal.swc\" needs to be downloaded from Adobe homepage:"); + System.err.println(adobePage); + System.err.println("Download the library called PlayerGlobal(.swc), and place it to directory"); + System.err.println(Configuration.getFlashLibPath().getAbsolutePath()); + System.exit(1); + } + + try { + pack.abc.replaceScriptPack(pack, as); + } catch (AVM2ParseException ex) { + System.err.println("%error% on line %line%".replace("%error%", ex.text).replace("%line%", Long.toString(ex.line))); + System.exit(1); + } catch (CompilationException ex) { + System.err.println("%error% on line %line%".replace("%error%", ex.text).replace("%line%", Long.toString(ex.line))); + System.exit(1); + } + } + + private static void parseDumpSwf(Stack args) { + if (args.isEmpty()) { + badArguments(); + } + try { + Configuration.dumpTags.set(true); + Configuration.parallelSpeedUp.set(false); + SWFSourceInfo sourceInfo = new SWFSourceInfo(null, args.pop(), null); + Main.parseSWF(sourceInfo); + } catch (Exception ex) { + logger.log(Level.SEVERE, null, ex); + System.exit(1); + } + System.exit(0); + } + + private static void parseDumpAS2(Stack args) { + if (args.isEmpty()) { + badArguments(); + } + File file = new File(args.pop()); + try { + try (FileInputStream is = new FileInputStream(file)) { + SWF swf = new SWF(is, Configuration.parallelSpeedUp.get()); + Map asms = swf.getASMs(false); + for (String as2 : asms.keySet()) { + System.out.println(as2); + } + } + } catch (IOException | InterruptedException e) { + System.err.println("I/O error during reading"); + System.exit(2); + } + } + + private static void parseDumpAS3(Stack args) { + if (args.isEmpty()) { + badArguments(); + } + File file = new File(args.pop()); + try { + try (FileInputStream is = new FileInputStream(file)) { + SWF swf = new SWF(is, Configuration.parallelSpeedUp.get()); + List packs = swf.getAS3Packs(); + for (ScriptPack entry : packs) { + System.out.println(entry.getClassPath().toString() + " " + entry.scriptIndex); + } + } + } catch (IOException | InterruptedException e) { + System.err.println("I/O error during reading"); + System.exit(2); + } + } + + private static FilenameFilter getSwfFilter() { + return (File dir, String name) -> name.toLowerCase().endsWith(".swf"); + } + + private static E enumFromStr(String str, Class cls) { + E[] vals = cls.getEnumConstants(); + if (str == null) { + return vals[0]; + } + for (E e : vals) { + if (e.toString().toLowerCase().replace("_", "").equals(str.toLowerCase().replace("_", ""))) { + return e; + } + } + return vals[0]; + } +}