do not use getBytesLength because it "generates" the action bytes to get the length (slow)

use instead getTotalActionLength
This commit is contained in:
honfika
2014-08-19 15:39:10 +02:00
parent 5130f3c2bb
commit 3b6cee4dfb
22 changed files with 316 additions and 289 deletions

View File

@@ -26,6 +26,7 @@ import com.jpexs.decompiler.flash.abc.ScriptPack;
import com.jpexs.decompiler.flash.action.Action;
import com.jpexs.decompiler.flash.action.ActionDeobfuscation;
import com.jpexs.decompiler.flash.action.ActionGraphSource;
import com.jpexs.decompiler.flash.action.ActionList;
import com.jpexs.decompiler.flash.action.ActionLocalData;
import com.jpexs.decompiler.flash.action.model.ConstantPool;
import com.jpexs.decompiler.flash.action.model.DirectValueActionItem;
@@ -1682,7 +1683,7 @@ public final class SWF implements TreeItem, Timelined {
GraphSourceItem ins = code.get(ip);
if (debugMode) {
System.err.println("Visit " + ip + ": ofs" + Helper.formatAddress(((Action) ins).getAddress()) + ":" + ((Action) ins).getASMSource(new ArrayList<GraphSourceItem>(), new ArrayList<Long>(), new ArrayList<String>(), code.version, ScriptExportMode.PCODE) + " stack:" + Helper.stackToString(stack, LocalData.create(new ConstantPool())));
System.err.println("Visit " + ip + ": ofs" + Helper.formatAddress(((Action) ins).getAddress()) + ":" + ((Action) ins).getASMSource(new ArrayList<GraphSourceItem>(), new ArrayList<Long>(), new ArrayList<String>(), ScriptExportMode.PCODE) + " stack:" + Helper.stackToString(stack, LocalData.create(new ConstantPool())));
}
if (ins.isExit()) {
break;
@@ -1844,12 +1845,12 @@ public final class SWF implements TreeItem, Timelined {
private List<MyEntry<DirectValueActionItem, ConstantPool>> getVariables(List<MyEntry<DirectValueActionItem, ConstantPool>> variables, List<GraphSourceItem> functions, HashMap<DirectValueActionItem, ConstantPool> strings, HashMap<DirectValueActionItem, String> usageType, ASMSource src, String path) throws InterruptedException {
List<MyEntry<DirectValueActionItem, ConstantPool>> ret = new ArrayList<>();
List<Action> actions = src.getActions();
ActionList actions = src.getActions();
actionsMap.put(src, actions);
getVariables(variables, functions, strings, usageType, new ActionGraphSource(actions, version, new HashMap<Integer, String>(), new HashMap<String, GraphTargetItem>(), new HashMap<String, GraphTargetItem>()), 0, path);
return ret;
}
private HashMap<ASMSource, List<Action>> actionsMap = new HashMap<>();
private HashMap<ASMSource, ActionList> actionsMap = new HashMap<>();
private void getVariables(List<ContainerItem> objs, String path) throws InterruptedException {
List<String> processed = new ArrayList<>();
@@ -2155,7 +2156,7 @@ public final class SWF implements TreeItem, Timelined {
}
}
for (ASMSource src : actionsMap.keySet()) {
actionsMap.put(src, Action.removeNops(0, actionsMap.get(src), version, ""/*FIXME path*/));
actionsMap.get(src).removeNops();
src.setActions(actionsMap.get(src));
src.setModified();
}

View File

@@ -323,6 +323,16 @@ public class Action implements GraphSourceItem {
return getBytes(version).length;
}
/**
* Uptates the action length to the length calculated from action bytes
*
* @param version SWF version
*/
public void updateLength(int version) {
int length = getBytes(version).length;
actionLength = length - 1 - ((actionCode >= 0x80) ? 2 : 0);
}
/**
* Surrounds byte array with Action header
*
@@ -374,13 +384,12 @@ public class Action implements GraphSourceItem {
*
* @param list List of actions
* @param baseAddress Address of first action in the list
* @param version SWF version
*/
public static void setActionsAddresses(List<Action> list, long baseAddress, int version) {
public static void setActionsAddresses(List<Action> list, long baseAddress) {
long offset = baseAddress;
for (Action a : list) {
a.setAddress(offset);
offset += a.getBytesLength(version);
offset += a.getTotalActionLength();
}
}
@@ -493,7 +502,7 @@ public class Action implements GraphSourceItem {
if (pos + 1 < list.size()) {
len = (int) (((Action) (list.get(pos + 1))).getAddress() - a.getAddress());
} else {
len = a.getBytesLength(version);
len = a.getTotalActionLength();
}
if (!(a instanceof ActionEnd)) {
for (int i = 0; i < len; i++) {
@@ -538,12 +547,12 @@ public class Action implements GraphSourceItem {
writer.appendNoHilight("FFDec_DeobfuscatePop").newLine();
if (fixBranch == 0) { //jump
writer.appendNoHilight("Jump loc");
writer.appendNoHilight(Helper.formatAddress(a.getAddress() + a.getBytesLength(version) + ((ActionIf) a).getJumpOffset()));
writer.appendNoHilight(Helper.formatAddress(a.getAddress() + a.getTotalActionLength() + ((ActionIf) a).getJumpOffset()));
} else {
//nojump, ignore
}
} else {
a.getASMSourceReplaced(list, importantOffsets, constantPool, version, exportMode, writer);
a.getASMSourceReplaced(list, importantOffsets, constantPool, exportMode, writer);
}
writer.appendNoHilight(a.isIgnored() ? "; ignored" : "");
writer.appendNoHilight(add);
@@ -593,7 +602,7 @@ public class Action implements GraphSourceItem {
* @param exportMode PCode or hex?
* @return String of P-code source
*/
public String getASMSource(List<? extends GraphSourceItem> container, List<Long> knownAddreses, List<String> constantPool, int version, ScriptExportMode exportMode) {
public String getASMSource(List<? extends GraphSourceItem> container, List<Long> knownAddreses, List<String> constantPool, ScriptExportMode exportMode) {
return toString();
}
@@ -633,10 +642,9 @@ public class Action implements GraphSourceItem {
*
* @param actions List of actions
* @param ip Action index
* @param version SWF version
* @return address
*/
public static long ip2adr(List<Action> actions, int ip, int version) {
public static long ip2adr(List<Action> actions, int ip) {
/* List<Action> actions=new ArrayList<Action>();
for(GraphSourceItem s:sources){
if(s instanceof Action){
@@ -647,7 +655,7 @@ public class Action implements GraphSourceItem {
if (actions.isEmpty()) {
return 0;
}
return actions.get(actions.size() - 1).getAddress() + actions.get(actions.size() - 1).getBytesLength(version);
return actions.get(actions.size() - 1).getAddress() + actions.get(actions.size() - 1).getTotalActionLength();
}
if (ip == -1) {
return 0;
@@ -660,17 +668,16 @@ public class Action implements GraphSourceItem {
*
* @param actions List of actions
* @param addr Address
* @param version SWF version
* @return action index
*/
public static int adr2ip(List<Action> actions, long addr, int version) {
public static int adr2ip(List<Action> actions, long addr) {
for (int ip = 0; ip < actions.size(); ip++) {
if (actions.get(ip).getAddress() == addr) {
return ip;
}
}
if (actions.size() > 0) {
long outpos = actions.get(actions.size() - 1).getAddress() + actions.get(actions.size() - 1).getBytesLength(version);
long outpos = actions.get(actions.size() - 1).getAddress() + actions.get(actions.size() - 1).getTotalActionLength();
if (addr == outpos) {
return actions.size();
}
@@ -824,7 +831,7 @@ public class Action implements GraphSourceItem {
loopip:
while (ip <= end) {
long addr = ip2adr(actions, ip, version);
long addr = ip2adr(actions, ip);
if (ip > end) {
break;
}
@@ -858,7 +865,7 @@ public class Action implements GraphSourceItem {
}
List<GraphTargetItem> out;
try {
out = ActionGraph.translateViaGraph(cnt.getRegNames(), variables2, functions, actions.subList(adr2ip(actions, endAddr, version), adr2ip(actions, endAddr + size, version)), version, staticOperation, path + (cntName == null ? "" : "/" + cntName));
out = ActionGraph.translateViaGraph(cnt.getRegNames(), variables2, functions, actions.subList(adr2ip(actions, endAddr), adr2ip(actions, endAddr + size)), version, staticOperation, path + (cntName == null ? "" : "/" + cntName));
} catch (OutOfMemoryError | TranslateException | StackOverflowError ex2) {
Logger.getLogger(Action.class.getName()).log(Level.SEVERE, "Decompilation error in: " + path, ex2);
if (ex2 instanceof OutOfMemoryError) {
@@ -877,7 +884,7 @@ public class Action implements GraphSourceItem {
endAddr += size;
}
((GraphSourceItemContainer) action).translateContainer(outs, stack, output, registerNames, variables, functions);
ip = adr2ip(actions, endAddr, version);
ip = adr2ip(actions, endAddr);
continue;
}
@@ -888,7 +895,7 @@ public class Action implements GraphSourceItem {
if (actions.get(ip + 2) instanceof ActionNot) {
if (actions.get(ip + 3) instanceof ActionIf) {
ActionIf aif = (ActionIf) actions.get(ip + 3);
if (adr2ip(actions, ip2adr(actions, ip + 4, version) + aif.getJumpOffset(), version) == ip) {
if (adr2ip(actions, ip2adr(actions, ip + 4) + aif.getJumpOffset()) == ip) {
ip += 4;
continue;
}
@@ -1188,19 +1195,6 @@ public class Action implements GraphSourceItem {
return false;
}
public static List<Action> removeNops(long address, List<Action> actions, int version, String path) {
List<Action> ret = actions;
try {
HilightedTextWriter writer = new HilightedTextWriter(Configuration.getCodeFormatting(), false);
Action.actionsToString(new ArrayList<DisassemblyListener>(), address, ret, version, ScriptExportMode.PCODE, writer, path);
String s = writer.toString();
ret = ASMParser.parse(address, true, s, SWF.DEFAULT_VERSION, false);
} catch (IOException | ParseException ex) {
Logger.getLogger(SWFInputStream.class.getName()).log(Level.SEVERE, "parsing error. path: " + path, ex);
}
return ret;
}
public static void setConstantPool(List<? extends GraphSourceItem> actions, ConstantPool cpool) {
for (GraphSourceItem a : actions) {
if (a instanceof ActionPush) {
@@ -1221,8 +1215,8 @@ public class Action implements GraphSourceItem {
}
}
public GraphTextWriter getASMSourceReplaced(List<? extends GraphSourceItem> container, List<Long> knownAddreses, List<String> constantPool, int version, ScriptExportMode exportMode, GraphTextWriter writer) {
writer.appendNoHilight(getASMSource(container, knownAddreses, constantPool, version, exportMode));
public GraphTextWriter getASMSourceReplaced(List<? extends GraphSourceItem> container, List<Long> knownAddreses, List<String> constantPool, ScriptExportMode exportMode, GraphTextWriter writer) {
writer.appendNoHilight(getASMSource(container, knownAddreses, constantPool, exportMode));
return writer;
}

View File

@@ -17,6 +17,7 @@
package com.jpexs.decompiler.flash.action;
import com.jpexs.decompiler.flash.SWF;
import com.jpexs.decompiler.flash.action.special.ActionNop;
import java.util.ArrayList;
/**
@@ -29,6 +30,29 @@ public class ActionList extends ArrayList<Action> {
ActionListReader.removeAction(this, index, SWF.DEFAULT_VERSION, true);
}
public void removeAction(int index, int count) {
if (size() <= index + count - 1) {
// Can't remove count elements, only size - index is available
count = size() - index;
}
for (int i = 0; i < count; i++) {
ActionListReader.removeAction(this, index, SWF.DEFAULT_VERSION, true);
}
}
public void addAction(int index, Action action) {
ActionListReader.removeAction(this, index, SWF.DEFAULT_VERSION, true);
}
public void removeNops() {
for (int i = 0; i < size(); i++) {
if (get(i) instanceof ActionNop) {
removeAction(i);
}
}
}
public Action getByAddress(long address) {
for (Action action : this) {
if (action.getAddress() == address) {

View File

@@ -90,7 +90,7 @@ public class ActionListReader {
* @throws java.lang.InterruptedException
* @throws java.util.concurrent.TimeoutException
*/
public static List<Action> readActionListTimeout(final List<DisassemblyListener> listeners, final SWFInputStream sis, final int version, final int ip, final int endIp, final String path) throws IOException, InterruptedException, TimeoutException {
public static ActionList readActionListTimeout(final List<DisassemblyListener> listeners, final SWFInputStream sis, final int version, final int ip, final int endIp, final String path) throws IOException, InterruptedException, TimeoutException {
try {
ActionList actions = CancellableWorker.call(new Callable<ActionList>() {
@@ -111,7 +111,7 @@ public class ActionListReader {
Logger.getLogger(ActionListReader.class.getName()).log(Level.SEVERE, null, ex);
}
}
return new ArrayList<>();
return new ActionList();
}
/**
@@ -180,7 +180,8 @@ public class ActionListReader {
Map<Action, Action> jumps = new HashMap<>();
getJumps(actions, jumps);
long endAddress = updateAddresses(actions, 0, version);
updateActionLengths(actions, version);
long endAddress = updateAddresses(actions, 0);
// add end action
Action lastAction = actions.get(actions.size() - 1);
@@ -194,17 +195,16 @@ public class ActionListReader {
updateJumps(actions, jumps, containerLastActions, endAddress);
updateActionStores(actions, jumps);
updateActionLengths(actions, version);
updateContainerSizes(actions, containerLastActions);
if (SWFDecompilerPlugin.listener != null) {
try {
SWFDecompilerPlugin.listener.actionListParsed(actions);
updateAddresses(actions, 0, version);
updateActionLengths(actions, version);
updateAddresses(actions, 0);
updateJumps(actions, jumps, containerLastActions, endAddress);
updateActionStores(actions, jumps);
updateActionLengths(actions, version);
updateContainerSizes(actions, containerLastActions);
} catch (Throwable e) {
View.showMessageDialog(null, "Failed to call plugin method actionListParsed. Exception: " + e.getMessage());
@@ -297,7 +297,7 @@ public class ActionListReader {
new HashMap<Integer, HashMap<String, GraphTargetItem>>(),
version, 0, maxRecursionLevel);
List<Action> ret = new ArrayList<>();
ActionList ret = new ActionList();
Action last = null;
for (Action a : retdups) {
if (a != last) {
@@ -305,7 +305,7 @@ public class ActionListReader {
}
last = a;
}
ret = Action.removeNops(0, ret, version, path);
ret.removeNops();
ActionList reta = new ActionList();
for (Object o : ret) {
if (o instanceof Action) {
@@ -403,11 +403,11 @@ public class ActionListReader {
}
}
private static long updateAddresses(List<Action> actions, long address, int version) {
private static long updateAddresses(List<Action> actions, long address) {
for (int i = 0; i < actions.size(); i++) {
Action a = actions.get(i);
a.setAddress(address);
int length = a.getBytesLength(version);
int length = a.getTotalActionLength();
if ((i != actions.size() - 1) && (a instanceof ActionEnd)) {
// placeholder for jump action
length = new ActionDeobfuscateJump(0).getTotalActionLength();
@@ -419,9 +419,7 @@ public class ActionListReader {
private static void updateActionLengths(List<Action> actions, int version) {
for (int i = 0; i < actions.size(); i++) {
Action a = actions.get(i);
int length = a.getBytesLength(version);
a.actionLength = length - 1 - ((a.actionCode >= 0x80) ? 2 : 0);
actions.get(i).updateLength(version);
}
}
@@ -538,6 +536,7 @@ public class ActionListReader {
/**
* Removes an action from the action list, and updates all references
* This method will keep the inner actions of the container when you remove the container
*
* @param actions
* @param index
@@ -596,10 +595,10 @@ public class ActionListReader {
actions.remove(index);
updateAddresses(actions, startIp, version);
updateActionLengths(actions, version);
updateAddresses(actions, startIp);
updateJumps(actions, jumps, containerLastActions, endAddress);
updateActionStores(actions, jumps);
updateActionLengths(actions, version);
updateContainerSizes(actions, containerLastActions);
return true;
@@ -745,7 +744,7 @@ public class ActionListReader {
}
if (debugMode) {
String atos = a.getASMSource(new ArrayList<GraphSourceItem>(), new ArrayList<Long>(), cpool.constants, version, ScriptExportMode.PCODE);
String atos = a.getASMSource(new ArrayList<GraphSourceItem>(), new ArrayList<Long>(), cpool.constants, ScriptExportMode.PCODE);
if (a instanceof GraphSourceItemContainer) {
atos = a.toString();
}

View File

@@ -141,7 +141,7 @@ public class ASMParser {
ActionConstantPool cpool = new ActionConstantPool(constantPool);
cpool.setAddress(address);
address += cpool.getBytesLength(version);
address += cpool.getTotalActionLength();
list.add(cpool);
while (true) {
@@ -185,7 +185,7 @@ public class ASMParser {
address += 1;
} else if (a != null) {
a.setAddress(address);
address += a.getBytesLength(version);
address += a.getTotalActionLength();
}
if (a instanceof GraphSourceItemContainer) {
containers.push((GraphSourceItemContainer) a);
@@ -415,6 +415,8 @@ public class ASMParser {
} else {
throw new ParseException("Unknown instruction name :" + instructionName, lexer.yyline());
}
a.updateLength(version);
return a;
}
@@ -473,19 +475,21 @@ public class ASMParser {
identifier = ((ActionJump) link).identifier;
for (Label label : labels) {
ActionJump actionJump = (ActionJump) link;
if (((ActionJump) link).identifier.equals(label.name)) {
((ActionJump) link).setJumpOffset((int) (label.address - (((ActionJump) link).getAddress() + ((ActionJump) link).getBytesLength(version))));
if (actionJump.identifier.equals(label.name)) {
actionJump.setJumpOffset((int) (label.address - (actionJump.getAddress() + actionJump.getTotalActionLength())));
found = true;
break;
}
}
} else if (link instanceof ActionIf) {
identifier = ((ActionIf) link).identifier;
ActionIf actionIf = (ActionIf) link;
identifier = actionIf.identifier;
for (Label label : labels) {
if (((ActionIf) link).identifier.equals(label.name)) {
((ActionIf) link).setJumpOffset((int) (label.address - (((ActionIf) link).getAddress() + ((ActionIf) link).getBytesLength(version))));
if (actionIf.identifier.equals(label.name)) {
actionIf.setJumpOffset((int) (label.address - (actionIf.getAddress() + actionIf.getTotalActionLength())));
found = true;
break;
}

View File

@@ -157,7 +157,7 @@ public class ActionSourceGenerator implements SourceGenerator {
|| (((ActionJump) onTrue.get(onTrue.size() - 1)).isBreak)))) {
ajmp = new ActionJump(0);
ret.add(ajmp);
onTrueLen += ajmp.getBytesLength(SWF.DEFAULT_VERSION);
onTrueLen += ajmp.getTotalActionLength();
}
ifaif.setJumpOffset(onTrueLen);
byte[] onFalseBytes = Action.actionsToBytes(onFalse, false, SWF.DEFAULT_VERSION);
@@ -182,7 +182,7 @@ public class ActionSourceGenerator implements SourceGenerator {
private void fixLoop(List<Action> code, int breakOffset, int continueOffset) {
int pos = 0;
for (Action a : code) {
pos += a.getBytesLength(SWF.DEFAULT_VERSION);
pos += a.getTotalActionLength();
if (a instanceof ActionJump) {
ActionJump aj = (ActionJump) a;
if (aj.isContinue && (continueOffset != Integer.MAX_VALUE)) {
@@ -248,7 +248,7 @@ public class ActionSourceGenerator implements SourceGenerator {
ret.addAll(doExpr);
ActionIf doif = new ActionIf(0);
ret.add(doif);
int offset = doBodyLen + doExprLen + doif.getBytesLength(SWF.DEFAULT_VERSION);
int offset = doBodyLen + doExprLen + doif.getTotalActionLength();
doif.setJumpOffset(-offset);
fixLoop(doBody, offset, doBodyLen);
return ret;
@@ -265,7 +265,7 @@ public class ActionSourceGenerator implements SourceGenerator {
ActionIf foraif = new ActionIf(0);
forExpr.add(foraif);
ActionJump forajmp = new ActionJump(0);
int forajmpLen = forajmp.getBytesLength(SWF.DEFAULT_VERSION);
int forajmpLen = forajmp.getTotalActionLength();
int forExprLen = Action.actionsToBytes(forExpr, false, SWF.DEFAULT_VERSION).length;
int forBodyLen = Action.actionsToBytes(forBody, false, SWF.DEFAULT_VERSION).length;
int forFinalLen = Action.actionsToBytes(forFinalCommands, false, SWF.DEFAULT_VERSION).length;
@@ -373,7 +373,7 @@ public class ActionSourceGenerator implements SourceGenerator {
jmpPos += exprLengths.get(k).get(m);
}
}
jmpPos += defJump.getBytesLength(SWF.DEFAULT_VERSION);
jmpPos += defJump.getTotalActionLength();
for (int n = 0; n < i; n++) {
jmpPos += caseLengths.get(n);
}

View File

@@ -56,7 +56,7 @@ public class ActionWaitForFrame extends Action implements ActionStore {
}
@Override
public String getASMSource(List<? extends GraphSourceItem> container, List<Long> knownAddreses, List<String> constantPool, int version, ScriptExportMode exportMode) {
public String getASMSource(List<? extends GraphSourceItem> container, List<Long> knownAddreses, List<String> constantPool, ScriptExportMode exportMode) {
String ret = "WaitForFrame " + frame + " " + skipCount;
return ret;
}

View File

@@ -75,8 +75,8 @@ public class ActionIf extends Action {
}
@Override
public String getASMSource(List<? extends GraphSourceItem> container, List<Long> knownAddreses, List<String> constantPool, int version, ScriptExportMode exportMode) {
String ofsStr = Helper.formatAddress(getAddress() + getBytesLength(version) + offset);
public String getASMSource(List<? extends GraphSourceItem> container, List<Long> knownAddreses, List<String> constantPool, ScriptExportMode exportMode) {
String ofsStr = Helper.formatAddress(getAddress() + getTotalActionLength() + offset);
return "If loc" + ofsStr + (!jumpUsed ? " ;compileTimeIgnore" : (!ignoreUsed ? " ;compileTimeJump" : ""));
}
@@ -98,8 +98,7 @@ public class ActionIf extends Action {
@Override
public List<Integer> getBranches(GraphSource code) {
List<Integer> ret = super.getBranches(code);
int version = ((ActionGraphSource) code).version;
int length = getBytesLength(version);
int length = getTotalActionLength();
int jmp = code.adr2pos(getAddress() + length + offset);
int after = code.adr2pos(getAddress() + length);
if (jmp == -1) {

View File

@@ -75,8 +75,8 @@ public class ActionJump extends Action {
}
@Override
public String getASMSource(List<? extends GraphSourceItem> container, List<Long> knownAddreses, List<String> constantPool, int version, ScriptExportMode exportMode) {
String ofsStr = Helper.formatAddress(getAddress() + getBytesLength(version) + offset);
public String getASMSource(List<? extends GraphSourceItem> container, List<Long> knownAddreses, List<String> constantPool, ScriptExportMode exportMode) {
String ofsStr = Helper.formatAddress(getAddress() + getTotalActionLength() + offset);
return "Jump loc" + ofsStr;
}

View File

@@ -231,7 +231,7 @@ public class ActionPush extends Action {
}
@Override
public GraphTextWriter getASMSourceReplaced(List<? extends GraphSourceItem> container, List<Long> knownAddreses, List<String> constantPool, int version, ScriptExportMode exportMode, GraphTextWriter writer) {
public GraphTextWriter getASMSourceReplaced(List<? extends GraphSourceItem> container, List<Long> knownAddreses, List<String> constantPool, ScriptExportMode exportMode, GraphTextWriter writer) {
if (replacement == null || replacement.size() < values.size()) {
return toString(writer);
}

View File

@@ -116,7 +116,7 @@ public class ActionWaitForFrame2 extends Action implements ActionStore {
}
@Override
public String getASMSource(List<? extends GraphSourceItem> container, List<Long> knownAddreses, List<String> constantPool, int version, ScriptExportMode exportMode) {
public String getASMSource(List<? extends GraphSourceItem> container, List<Long> knownAddreses, List<String> constantPool, ScriptExportMode exportMode) {
String ret = "WaitForFrame2 " + skipCount;
/*for (int i = 0; i < skipped.size(); i++) {
if (skipped.get(i) instanceof ActionEnd) {

View File

@@ -146,7 +146,7 @@ public class ActionDefineFunction extends Action implements GraphSourceItemConta
}
@Override
public String getASMSource(List<? extends GraphSourceItem> container, List<Long> knownAddreses, List<String> constantPool, int version, ScriptExportMode exportMode) {
public String getASMSource(List<? extends GraphSourceItem> container, List<Long> knownAddreses, List<String> constantPool, ScriptExportMode exportMode) {
StringBuilder paramStr = new StringBuilder();
for (int i = 0; i < paramNames.size(); i++) {
paramStr.append("\"").append(Helper.escapeString(paramNames.get(i))).append("\" ");
@@ -156,7 +156,7 @@ public class ActionDefineFunction extends Action implements GraphSourceItemConta
}
@Override
public GraphTextWriter getASMSourceReplaced(List<? extends GraphSourceItem> container, List<Long> knownAddreses, List<String> constantPool, int version, ScriptExportMode exportMode, GraphTextWriter writer) {
public GraphTextWriter getASMSourceReplaced(List<? extends GraphSourceItem> container, List<Long> knownAddreses, List<String> constantPool, ScriptExportMode exportMode, GraphTextWriter writer) {
List<String> oldParamNames = paramNames;
if (replacedParamNames != null) {
paramNames = replacedParamNames;
@@ -165,7 +165,7 @@ public class ActionDefineFunction extends Action implements GraphSourceItemConta
if (replacedFunctionName != null) {
functionName = replacedFunctionName;
}
String ret = getASMSource(container, knownAddreses, constantPool, version, exportMode);
String ret = getASMSource(container, knownAddreses, constantPool, exportMode);
paramNames = oldParamNames;
functionName = oldFunctionName;
writer.appendNoHilight(ret);

View File

@@ -75,7 +75,7 @@ public class ActionWith extends Action implements GraphSourceItemContainer {
}
@Override
public String getASMSource(List<? extends GraphSourceItem> container, List<Long> knownAddreses, List<String> constantPool, int version, ScriptExportMode exportMode) {
public String getASMSource(List<? extends GraphSourceItem> container, List<Long> knownAddreses, List<String> constantPool, ScriptExportMode exportMode) {
return "With {";
}

View File

@@ -226,7 +226,7 @@ public class ActionDefineFunction2 extends Action implements GraphSourceItemCont
}
@Override
public GraphTextWriter getASMSourceReplaced(List<? extends GraphSourceItem> container, List<Long> knownAddreses, List<String> constantPool, int version, ScriptExportMode exportMode, GraphTextWriter writer) {
public GraphTextWriter getASMSourceReplaced(List<? extends GraphSourceItem> container, List<Long> knownAddreses, List<String> constantPool, ScriptExportMode exportMode, GraphTextWriter writer) {
List<String> oldParamNames = paramNames;
if (replacedParamNames != null) {
paramNames = replacedParamNames;
@@ -235,7 +235,7 @@ public class ActionDefineFunction2 extends Action implements GraphSourceItemCont
if (replacedFunctionName != null) {
functionName = replacedFunctionName;
}
String ret = getASMSource(container, knownAddreses, constantPool, version, exportMode);
String ret = getASMSource(container, knownAddreses, constantPool, exportMode);
paramNames = oldParamNames;
functionName = oldFunctionName;
writer.appendNoHilight(ret);
@@ -244,7 +244,7 @@ public class ActionDefineFunction2 extends Action implements GraphSourceItemCont
}
@Override
public String getASMSource(List<? extends GraphSourceItem> container, List<Long> knownAddreses, List<String> constantPool, int version, ScriptExportMode exportMode) {
public String getASMSource(List<? extends GraphSourceItem> container, List<Long> knownAddreses, List<String> constantPool, ScriptExportMode exportMode) {
StringBuilder paramStr = new StringBuilder();
for (int i = 0; i < paramNames.size(); i++) {
paramStr.append(paramRegisters.get(i)).append(" \"").append(Helper.escapeString(paramNames.get(i))).append("\" ");

View File

@@ -148,7 +148,7 @@ public class ActionTry extends Action implements GraphSourceItemContainer {
}
@Override
public String getASMSource(List<? extends GraphSourceItem> container, List<Long> knownAddreses, List<String> constantPool, int version, ScriptExportMode exportMode) {
public String getASMSource(List<? extends GraphSourceItem> container, List<Long> knownAddreses, List<String> constantPool, ScriptExportMode exportMode) {
String ret = "";
ret += "Try ";
if (catchBlockFlag) {

View File

@@ -21,6 +21,7 @@ import com.jpexs.decompiler.flash.SWFInputStream;
import com.jpexs.decompiler.flash.SWFOutputStream;
import com.jpexs.decompiler.flash.abc.CopyOutputStream;
import com.jpexs.decompiler.flash.action.Action;
import com.jpexs.decompiler.flash.action.ActionList;
import com.jpexs.decompiler.flash.action.ActionListReader;
import com.jpexs.decompiler.flash.configuration.Configuration;
import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode;
@@ -165,20 +166,20 @@ public class DefineButtonTag extends ButtonTag implements ASMSource {
* @throws java.lang.InterruptedException
*/
@Override
public List<Action> getActions() throws InterruptedException {
public ActionList getActions() throws InterruptedException {
try {
int prevLength = actionBytes.pos;
SWFInputStream rri = new SWFInputStream(swf, actionBytes.array);
if (prevLength != 0) {
rri.seek(prevLength);
}
List<Action> list = ActionListReader.readActionListTimeout(listeners, rri, getVersion(), prevLength, prevLength + actionBytes.length, toString()/*FIXME?*/);
ActionList list = ActionListReader.readActionListTimeout(listeners, rri, getVersion(), prevLength, prevLength + actionBytes.length, toString()/*FIXME?*/);
return list;
} catch (InterruptedException ex) {
throw ex;
} catch (Exception ex) {
Logger.getLogger(DoActionTag.class.getName()).log(Level.SEVERE, null, ex);
return new ArrayList<>();
return new ActionList();
}
}

View File

@@ -20,6 +20,7 @@ import com.jpexs.decompiler.flash.DisassemblyListener;
import com.jpexs.decompiler.flash.SWF;
import com.jpexs.decompiler.flash.SWFInputStream;
import com.jpexs.decompiler.flash.action.Action;
import com.jpexs.decompiler.flash.action.ActionList;
import com.jpexs.decompiler.flash.action.ActionListReader;
import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode;
import com.jpexs.decompiler.flash.helpers.GraphTextWriter;
@@ -119,20 +120,20 @@ public class DoActionTag extends Tag implements ASMSource {
}
@Override
public List<Action> getActions() throws InterruptedException {
public ActionList getActions() throws InterruptedException {
try {
int prevLength = actionBytes.pos;
SWFInputStream rri = new SWFInputStream(swf, actionBytes.array);
if (prevLength != 0) {
rri.seek(prevLength);
}
List<Action> list = ActionListReader.readActionListTimeout(listeners, rri, getVersion(), prevLength, prevLength + actionBytes.length, toString()/*FIXME?*/);
ActionList list = ActionListReader.readActionListTimeout(listeners, rri, getVersion(), prevLength, prevLength + actionBytes.length, toString()/*FIXME?*/);
return list;
} catch (InterruptedException ex) {
throw ex;
} catch (Exception ex) {
Logger.getLogger(DoActionTag.class.getName()).log(Level.SEVERE, null, ex);
return new ArrayList<>();
return new ActionList();
}
}

View File

@@ -20,6 +20,7 @@ import com.jpexs.decompiler.flash.DisassemblyListener;
import com.jpexs.decompiler.flash.SWFInputStream;
import com.jpexs.decompiler.flash.SWFOutputStream;
import com.jpexs.decompiler.flash.action.Action;
import com.jpexs.decompiler.flash.action.ActionList;
import com.jpexs.decompiler.flash.action.ActionListReader;
import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode;
import com.jpexs.decompiler.flash.helpers.GraphTextWriter;
@@ -113,20 +114,20 @@ public class DoInitActionTag extends CharacterIdTag implements ASMSource {
}
@Override
public List<Action> getActions() throws InterruptedException {
public ActionList getActions() throws InterruptedException {
try {
int prevLength = actionBytes.pos;
SWFInputStream rri = new SWFInputStream(swf, actionBytes.array);
if (prevLength != 0) {
rri.seek(prevLength);
}
List<Action> list = ActionListReader.readActionListTimeout(listeners, rri, getVersion(), prevLength, prevLength + actionBytes.length, toString()/*FIXME?*/);
ActionList list = ActionListReader.readActionListTimeout(listeners, rri, getVersion(), prevLength, prevLength + actionBytes.length, toString()/*FIXME?*/);
return list;
} catch (InterruptedException ex) {
throw ex;
} catch (Exception ex) {
Logger.getLogger(DoActionTag.class.getName()).log(Level.SEVERE, null, ex);
return new ArrayList<>();
return new ActionList();
}
}

View File

@@ -18,6 +18,7 @@ package com.jpexs.decompiler.flash.tags.base;
import com.jpexs.decompiler.flash.DisassemblyListener;
import com.jpexs.decompiler.flash.action.Action;
import com.jpexs.decompiler.flash.action.ActionList;
import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode;
import com.jpexs.decompiler.flash.helpers.GraphTextWriter;
import com.jpexs.decompiler.flash.tags.Tag;
@@ -55,7 +56,7 @@ public interface ASMSource extends TreeItem {
* @return List of actions
* @throws java.lang.InterruptedException
*/
public List<Action> getActions() throws InterruptedException;
public ActionList getActions() throws InterruptedException;
/**
* Sets actions associated with this object

View File

@@ -1,192 +1,192 @@
/*
* Copyright (C) 2010-2014 JPEXS
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.jpexs.decompiler.flash.treenodes;
import com.jpexs.decompiler.flash.AbortRetryIgnoreHandler;
import com.jpexs.decompiler.flash.EventListener;
import com.jpexs.decompiler.flash.action.Action;
import com.jpexs.decompiler.flash.configuration.Configuration;
import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode;
import com.jpexs.decompiler.flash.helpers.FileTextWriter;
import com.jpexs.decompiler.flash.tags.Tag;
import com.jpexs.decompiler.flash.tags.base.ASMSource;
import com.jpexs.decompiler.flash.tags.base.Exportable;
import com.jpexs.decompiler.graph.TranslateException;
import com.jpexs.helpers.CancellableWorker;
import com.jpexs.helpers.Helper;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
public class TagNode extends ContainerNode {
public TagNode(Tag tag) {
super(tag);
}
@Override
public Tag getItem() {
return (Tag) item;
}
public static void setExport(List<TreeNode> nodeList, boolean export) {
for (TreeNode node : nodeList) {
node.export = export;
setExport(node.subNodes, export);
}
}
public static int getTagCountRecursive(List<TreeNode> nodeList) {
int count = 0;
for (TreeNode node : nodeList) {
if (node.subNodes.isEmpty()) {
if ((node.item instanceof ASMSource) && (node.export)) {
count += 1;
}
} else {
count += getTagCountRecursive(node.subNodes);
}
}
return count;
}
public static List<File> exportNodeAS(final AbortRetryIgnoreHandler handler, final List<TreeNode> nodeList, final String outdir, final ScriptExportMode exportMode, final EventListener ev) throws IOException {
try {
List<File> result = CancellableWorker.call(new Callable<List<File>>() {
@Override
public List<File> call() throws Exception {
AtomicInteger cnt = new AtomicInteger(1);
int totalCount = TagNode.getTagCountRecursive(nodeList);
return exportNodeAS(handler, nodeList, outdir, exportMode, cnt, totalCount, ev);
}
}, Configuration.exportTimeout.get(), TimeUnit.SECONDS);
return result;
} catch (ExecutionException | InterruptedException | TimeoutException ex) {
}
return new ArrayList<>();
}
private static List<File> exportNodeAS(AbortRetryIgnoreHandler handler, List<TreeNode> nodeList, String outdir, ScriptExportMode exportMode, AtomicInteger index, int count, EventListener ev) throws IOException {
File dir = new File(outdir);
List<File> ret = new ArrayList<>();
if (!outdir.endsWith(File.separator)) {
outdir += File.separator;
}
List<String> existingNames = new ArrayList<>();
for (TreeNode node : nodeList) {
String name = "";
if (node.item instanceof Exportable) {
name = Helper.makeFileName(((Exportable) node.item).getExportFileName());
} else {
name = Helper.makeFileName(node.item.toString());
}
int i = 1;
String baseName = name;
while (existingNames.contains(name)) {
i++;
name = baseName + "_" + i;
}
existingNames.add(name);
if (node.subNodes.isEmpty()) {
if ((node.item instanceof ASMSource) && (node.export)) {
boolean retry;
do {
retry = false;
try {
int currentIndex = index.getAndIncrement();
if (!dir.exists()) {
if (!dir.mkdirs()) {
if (!dir.exists()) {
throw new IOException("Cannot create directory " + outdir);
}
}
}
String f = outdir + name + ".as";
if (ev != null) {
ev.handleEvent("exporting", "Exporting " + currentIndex + "/" + count + " " + f);
}
long startTime = System.currentTimeMillis();
File file = new File(f);
ASMSource asm = ((ASMSource) node.item);
try (FileTextWriter writer = new FileTextWriter(Configuration.getCodeFormatting(), new FileOutputStream(f))) {
if (exportMode == ScriptExportMode.HEX) {
asm.getActionSourcePrefix(writer);
asm.getActionBytesAsHex(writer);
asm.getActionSourceSuffix(writer);
} else if (exportMode != ScriptExportMode.AS) {
asm.getActionSourcePrefix(writer);
asm.getASMSource(exportMode, writer, null);
asm.getActionSourceSuffix(writer);
} else {
List<Action> as = asm.getActions();
Action.setActionsAddresses(as, 0, asm.getSwf().version);
Action.actionsToSource(asm, as, ""/*FIXME*/, writer);
}
}
long stopTime = System.currentTimeMillis();
if (ev != null) {
long time = stopTime - startTime;
ev.handleEvent("exported", "Exported " + currentIndex + "/" + count + " " + f + ", " + Helper.formatTimeSec(time));
}
ret.add(file);
} catch (InterruptedException ex) {
} catch (IOException | OutOfMemoryError | TranslateException | StackOverflowError ex) {
Logger.getLogger(TagNode.class.getName()).log(Level.SEVERE, "Decompilation error in file: " + name + ".as", ex);
if (handler != null) {
int action = handler.getNewInstance().handle(ex);
switch (action) {
case AbortRetryIgnoreHandler.ABORT:
throw ex;
case AbortRetryIgnoreHandler.RETRY:
retry = true;
break;
case AbortRetryIgnoreHandler.IGNORE:
retry = false;
break;
}
}
}
} while (retry);
}
} else {
ret.addAll(exportNodeAS(handler, node.subNodes, outdir + name, exportMode, index, count, ev));
}
}
return ret;
}
}
/*
* Copyright (C) 2010-2014 JPEXS
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.jpexs.decompiler.flash.treenodes;
import com.jpexs.decompiler.flash.AbortRetryIgnoreHandler;
import com.jpexs.decompiler.flash.EventListener;
import com.jpexs.decompiler.flash.action.Action;
import com.jpexs.decompiler.flash.configuration.Configuration;
import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode;
import com.jpexs.decompiler.flash.helpers.FileTextWriter;
import com.jpexs.decompiler.flash.tags.Tag;
import com.jpexs.decompiler.flash.tags.base.ASMSource;
import com.jpexs.decompiler.flash.tags.base.Exportable;
import com.jpexs.decompiler.graph.TranslateException;
import com.jpexs.helpers.CancellableWorker;
import com.jpexs.helpers.Helper;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
public class TagNode extends ContainerNode {
public TagNode(Tag tag) {
super(tag);
}
@Override
public Tag getItem() {
return (Tag) item;
}
public static void setExport(List<TreeNode> nodeList, boolean export) {
for (TreeNode node : nodeList) {
node.export = export;
setExport(node.subNodes, export);
}
}
public static int getTagCountRecursive(List<TreeNode> nodeList) {
int count = 0;
for (TreeNode node : nodeList) {
if (node.subNodes.isEmpty()) {
if ((node.item instanceof ASMSource) && (node.export)) {
count += 1;
}
} else {
count += getTagCountRecursive(node.subNodes);
}
}
return count;
}
public static List<File> exportNodeAS(final AbortRetryIgnoreHandler handler, final List<TreeNode> nodeList, final String outdir, final ScriptExportMode exportMode, final EventListener ev) throws IOException {
try {
List<File> result = CancellableWorker.call(new Callable<List<File>>() {
@Override
public List<File> call() throws Exception {
AtomicInteger cnt = new AtomicInteger(1);
int totalCount = TagNode.getTagCountRecursive(nodeList);
return exportNodeAS(handler, nodeList, outdir, exportMode, cnt, totalCount, ev);
}
}, Configuration.exportTimeout.get(), TimeUnit.SECONDS);
return result;
} catch (ExecutionException | InterruptedException | TimeoutException ex) {
}
return new ArrayList<>();
}
private static List<File> exportNodeAS(AbortRetryIgnoreHandler handler, List<TreeNode> nodeList, String outdir, ScriptExportMode exportMode, AtomicInteger index, int count, EventListener ev) throws IOException {
File dir = new File(outdir);
List<File> ret = new ArrayList<>();
if (!outdir.endsWith(File.separator)) {
outdir += File.separator;
}
List<String> existingNames = new ArrayList<>();
for (TreeNode node : nodeList) {
String name = "";
if (node.item instanceof Exportable) {
name = Helper.makeFileName(((Exportable) node.item).getExportFileName());
} else {
name = Helper.makeFileName(node.item.toString());
}
int i = 1;
String baseName = name;
while (existingNames.contains(name)) {
i++;
name = baseName + "_" + i;
}
existingNames.add(name);
if (node.subNodes.isEmpty()) {
if ((node.item instanceof ASMSource) && (node.export)) {
boolean retry;
do {
retry = false;
try {
int currentIndex = index.getAndIncrement();
if (!dir.exists()) {
if (!dir.mkdirs()) {
if (!dir.exists()) {
throw new IOException("Cannot create directory " + outdir);
}
}
}
String f = outdir + name + ".as";
if (ev != null) {
ev.handleEvent("exporting", "Exporting " + currentIndex + "/" + count + " " + f);
}
long startTime = System.currentTimeMillis();
File file = new File(f);
ASMSource asm = ((ASMSource) node.item);
try (FileTextWriter writer = new FileTextWriter(Configuration.getCodeFormatting(), new FileOutputStream(f))) {
if (exportMode == ScriptExportMode.HEX) {
asm.getActionSourcePrefix(writer);
asm.getActionBytesAsHex(writer);
asm.getActionSourceSuffix(writer);
} else if (exportMode != ScriptExportMode.AS) {
asm.getActionSourcePrefix(writer);
asm.getASMSource(exportMode, writer, null);
asm.getActionSourceSuffix(writer);
} else {
List<Action> as = asm.getActions();
Action.setActionsAddresses(as, 0);
Action.actionsToSource(asm, as, ""/*FIXME*/, writer);
}
}
long stopTime = System.currentTimeMillis();
if (ev != null) {
long time = stopTime - startTime;
ev.handleEvent("exported", "Exported " + currentIndex + "/" + count + " " + f + ", " + Helper.formatTimeSec(time));
}
ret.add(file);
} catch (InterruptedException ex) {
} catch (IOException | OutOfMemoryError | TranslateException | StackOverflowError ex) {
Logger.getLogger(TagNode.class.getName()).log(Level.SEVERE, "Decompilation error in file: " + name + ".as", ex);
if (handler != null) {
int action = handler.getNewInstance().handle(ex);
switch (action) {
case AbortRetryIgnoreHandler.ABORT:
throw ex;
case AbortRetryIgnoreHandler.RETRY:
retry = true;
break;
case AbortRetryIgnoreHandler.IGNORE:
retry = false;
break;
}
}
}
} while (retry);
}
} else {
ret.addAll(exportNodeAS(handler, node.subNodes, outdir + name, exportMode, index, count, ev));
}
}
return ret;
}
}

View File

@@ -20,6 +20,7 @@ import com.jpexs.decompiler.flash.DisassemblyListener;
import com.jpexs.decompiler.flash.SWF;
import com.jpexs.decompiler.flash.SWFInputStream;
import com.jpexs.decompiler.flash.action.Action;
import com.jpexs.decompiler.flash.action.ActionList;
import com.jpexs.decompiler.flash.action.ActionListReader;
import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode;
import com.jpexs.decompiler.flash.helpers.GraphTextWriter;
@@ -184,16 +185,16 @@ public class BUTTONCONDACTION implements ASMSource, Exportable, ContainerItem, S
* @throws java.lang.InterruptedException
*/
@Override
public List<Action> getActions() throws InterruptedException {
public ActionList getActions() throws InterruptedException {
try {
List<Action> list = ActionListReader.readActionListTimeout(listeners, new SWFInputStream(swf, actionBytes), swf.version, 0, -1, toString()/*FIXME?*/);
ActionList list = ActionListReader.readActionListTimeout(listeners, new SWFInputStream(swf, actionBytes), swf.version, 0, -1, toString()/*FIXME?*/);
return list;
} catch (InterruptedException ex) {
throw ex;
} catch (Exception ex) {
Logger.getLogger(BUTTONCONDACTION.class.getName()).log(Level.SEVERE, null, ex);
return new ArrayList<>();
return new ActionList();
}
}

View File

@@ -20,6 +20,7 @@ import com.jpexs.decompiler.flash.DisassemblyListener;
import com.jpexs.decompiler.flash.SWF;
import com.jpexs.decompiler.flash.SWFInputStream;
import com.jpexs.decompiler.flash.action.Action;
import com.jpexs.decompiler.flash.action.ActionList;
import com.jpexs.decompiler.flash.action.ActionListReader;
import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode;
import com.jpexs.decompiler.flash.helpers.GraphTextWriter;
@@ -194,15 +195,15 @@ public class CLIPACTIONRECORD implements ASMSource, Exportable, ContainerItem, S
}
@Override
public List<Action> getActions() throws InterruptedException {
public ActionList getActions() throws InterruptedException {
try {
List<Action> list = ActionListReader.readActionListTimeout(listeners, new SWFInputStream(swf, actionBytes), swf.version, 0, -1, toString()/*FIXME?*/);
ActionList list = ActionListReader.readActionListTimeout(listeners, new SWFInputStream(swf, actionBytes), swf.version, 0, -1, toString()/*FIXME?*/);
return list;
} catch (InterruptedException ex) {
throw ex;
} catch (Exception ex) {
Logger.getLogger(BUTTONCONDACTION.class.getName()).log(Level.SEVERE, null, ex);
return new ArrayList<>();
return new ActionList();
}
}