/*
* Copyright (C) 2010-2016 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.BaseLocalData;
import com.jpexs.decompiler.flash.abc.ABCOutputStream;
import com.jpexs.decompiler.flash.abc.AVM2LocalData;
import com.jpexs.decompiler.flash.abc.avm2.AVM2Code;
import com.jpexs.decompiler.flash.abc.avm2.AVM2ConstantPool;
import com.jpexs.decompiler.flash.abc.avm2.instructions.jumps.JumpIns;
import com.jpexs.decompiler.flash.abc.avm2.instructions.jumps.LookupSwitchIns;
import com.jpexs.decompiler.flash.abc.avm2.instructions.other.ReturnValueIns;
import com.jpexs.decompiler.flash.abc.avm2.instructions.other.ReturnVoidIns;
import com.jpexs.decompiler.flash.abc.avm2.instructions.other.ThrowIns;
import com.jpexs.decompiler.flash.abc.types.Float4;
import com.jpexs.decompiler.flash.abc.types.MethodBody;
import com.jpexs.decompiler.flash.abc.types.Multiname;
import com.jpexs.decompiler.flash.helpers.GraphTextWriter;
import com.jpexs.decompiler.graph.DottedChain;
import com.jpexs.decompiler.graph.GraphSource;
import com.jpexs.decompiler.graph.GraphSourceItem;
import com.jpexs.decompiler.graph.GraphTargetItem;
import com.jpexs.decompiler.graph.TranslateStack;
import com.jpexs.decompiler.graph.model.LocalData;
import com.jpexs.helpers.Helper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
*
* @author JPEXS
*/
public class AVM2Instruction implements Cloneable, GraphSourceItem {
public InstructionDefinition definition;
public int[] operands;
private long address;
public String comment;
private boolean ignored = false;
private int line;
private String file;
@Override
public long getFileOffset() {
return -1;
}
@Override
public long getLineOffset() {
return getAddress();
}
public void setFileLine(String file, int line) {
this.file = file;
this.line = line;
}
public AVM2Instruction(long offset, int insructionCode, int[] operands) {
this(offset, AVM2Code.instructionSet[insructionCode], operands);
}
public AVM2Instruction(long address, InstructionDefinition definition, int[] operands) {
this.definition = definition;
this.operands = operands != null && operands.length > 0 ? operands : null;
this.address = address;
}
public byte[] getBytes() {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
ABCOutputStream aos = new ABCOutputStream(bos);
aos.write(definition.instructionCode);
for (int i = 0; i < definition.operands.length; i++) {
int opt = definition.operands[i] & 0xff00;
switch (opt) {
case AVM2Code.OPT_S24:
aos.writeS24(operands[i]);
break;
case AVM2Code.OPT_U30:
case AVM2Code.OPT_S16:
aos.writeU30(operands[i]);
break;
case AVM2Code.OPT_U8:
aos.writeU8(operands[i]);
break;
case AVM2Code.OPT_S8:
aos.writeU8(0xff & operands[i]);
break;
case AVM2Code.OPT_CASE_OFFSETS:
aos.writeU30(operands[i]); //case count
for (int j = i + 1; j < operands.length; j++) {
aos.writeS24(operands[j]);
}
break;
}
}
} catch (IOException ex) {
// ignored
}
return bos.toByteArray();
}
public int getBytesLength() {
int cnt = 1;
for (int i = 0; i < definition.operands.length; i++) {
int opt = definition.operands[i] & 0xff00;
switch (opt) {
case AVM2Code.OPT_S24:
cnt += 3;
break;
case AVM2Code.OPT_U30:
case AVM2Code.OPT_S16:
cnt += ABCOutputStream.getU30ByteLength(operands[i]);
break;
case AVM2Code.OPT_U8:
cnt++;
break;
case AVM2Code.OPT_S8:
cnt++;
break;
case AVM2Code.OPT_CASE_OFFSETS:
cnt += ABCOutputStream.getU30ByteLength(operands[i]); //case count
for (int j = i + 1; j < operands.length; j++) {
cnt += 3;
}
break;
}
}
return cnt;
}
@Override
public String toString() {
StringBuilder s = new StringBuilder();
s.append(definition.instructionName);
if (operands != null) {
for (int i = 0; i < operands.length; i++) {
s.append(" ");
s.append(operands[i]);
}
}
return s.toString();
}
public List<Long> getOffsets() {
List<Long> ret = new ArrayList<>();
String s = "";
for (int i = 0; i < definition.operands.length; i++) {
switch (definition.operands[i]) {
case AVM2Code.DAT_OFFSET:
ret.add(address + operands[i] + getBytesLength());
break;
case AVM2Code.DAT_CASE_BASEOFFSET:
ret.add(address + operands[i]);
break;
case AVM2Code.OPT_CASE_OFFSETS:
for (int j = i + 1; j < operands.length; j++) {
ret.add(address + operands[j]);
}
break;
}
}
return ret;
}
public Object getParam(AVM2ConstantPool constants, int idx) {
//if (idx < 0 || idx >= definition.operands.length) {
// return null;
//}
switch (definition.operands[idx]) {
case AVM2Code.DAT_MULTINAME_INDEX:
return constants.getMultiname(operands[idx]);
case AVM2Code.DAT_STRING_INDEX:
return constants.getString(operands[idx]);
case AVM2Code.DAT_INT_INDEX:
return constants.getInt(operands[idx]);
case AVM2Code.DAT_UINT_INDEX:
return constants.getUInt(operands[idx]);
case AVM2Code.DAT_DOUBLE_INDEX:
return constants.getDouble(operands[idx]);
case AVM2Code.DAT_OFFSET:
return address + operands[idx] + getBytesLength();
case AVM2Code.DAT_CASE_BASEOFFSET:
return address + operands[idx];
case AVM2Code.OPT_CASE_OFFSETS:
return (long) operands[idx]; // offsets: offset + operands[i];
default:
return (long) operands[idx];
}
}
public Long getParamAsLong(AVM2ConstantPool constants, int idx) {
return (Long) getParam(constants, idx);
}
public String getParams(AVM2ConstantPool constants, List<DottedChain> fullyQualifiedNames) {
StringBuilder s = new StringBuilder();
for (int i = 0; i < definition.operands.length; i++) {
switch (definition.operands[i]) {
case AVM2Code.DAT_NAMESPACE_INDEX:
if (operands[i] == 0) {
s.append(" null");
} else {
s.append(" ");
s.append(Multiname.namespaceToString(constants, operands[i]));
}
break;
case AVM2Code.DAT_MULTINAME_INDEX:
if (operands[i] == 0) {
s.append(" null");
} else {
s.append(" ");
Multiname multiname = constants.getMultiname(operands[i]);
if (multiname != null) {
s.append(multiname.toString(constants, fullyQualifiedNames));
} else {
s.append("Multiname not found.");
}
}
/*s.append(" m[");
s.append(operands[i]);
s.append("]\"");
if (constants.constant_multiname[operands[i]] == null) {
s.append("");
} else {
s.append(Helper.escapeString(constants.constant_multiname[operands[i]].toString(constants, fullyQualifiedNames)));
}
s.append("\"");*/
break;
case AVM2Code.DAT_STRING_INDEX:
String str;
if (operands[i] == 0 || (str = constants.getString(operands[i])) == null) {
s.append(" null");
} else {
s.append(" \"");
s.append(Helper.escapeActionScriptString(str));
s.append("\"");
}
break;
case AVM2Code.DAT_INT_INDEX:
if (operands[i] == 0) {
s.append(" null");
} else {
s.append(" ");
s.append(constants.getInt(operands[i]));
}
break;
case AVM2Code.DAT_UINT_INDEX:
if (operands[i] == 0) {
s.append(" null");
} else {
s.append(" ");
s.append(constants.getUInt(operands[i]));
}
break;
case AVM2Code.DAT_DOUBLE_INDEX:
if (operands[i] == 0) {
s.append(" null");
} else {
s.append(" ");
s.append(constants.getDouble(operands[i]));
}
break;
case AVM2Code.DAT_FLOAT_INDEX:
if (operands[i] == 0) {
s.append(" null");
} else {
s.append(" ");
s.append(constants.getFloat(operands[i]));
}
break;
case AVM2Code.DAT_FLOAT4_INDEX:
if (operands[i] == 0) {
s.append(" null");
} else {
Float4 f4 = constants.getFloat4(operands[i]);
s.append(" ").append(f4.values[0]);
s.append(" ").append(f4.values[1]);
s.append(" ").append(f4.values[2]);
s.append(" ").append(f4.values[3]);
}
break;
case AVM2Code.DAT_DECIMAL_INDEX:
if (operands[i] == 0) {
s.append(" null");
} else {
s.append(" ");
s.append(constants.getDecimal(operands[i]));
}
break;
case AVM2Code.DAT_OFFSET:
s.append(" ");
s.append("ofs");
s.append(Helper.formatAddress(address + operands[i] + getBytesLength()));
break;
case AVM2Code.DAT_CASE_BASEOFFSET:
s.append(" ");
s.append("ofs");
s.append(Helper.formatAddress(address + operands[i]));
break;
case AVM2Code.OPT_CASE_OFFSETS:
s.append(" ");
s.append(operands[i]);
for (int j = i + 1; j < operands.length; j++) {
s.append(" ");
s.append("ofs");
s.append(Helper.formatAddress(address + operands[j]));
}
break;
default:
s.append(" ");
s.append(operands[i]);
}
}
return s.toString();
}
public String getComment() {
if (isIgnored()) {
return " ;ignored";
}
if ((comment == null) || comment.isEmpty()) {
return "";
}
return " ;" + comment;
}
@Override
public boolean isIgnored() {
return ignored;
}
public GraphTextWriter toString(GraphTextWriter writer, LocalData localData) {
writer.appendNoHilight(Helper.formatAddress(address) + " " + String.format("%-30s", Helper.byteArrToString(getBytes())) + definition.instructionName);
writer.appendNoHilight(getParams(localData.constantsAvm2, localData.fullyQualifiedNames) + getComment());
return writer;
}
public String toStringNoAddress(AVM2ConstantPool constants, List<DottedChain> fullyQualifiedNames) {
String s = definition.instructionName;
s += getParams(constants, fullyQualifiedNames) + getComment();
return s;
}
@Override
public void translate(BaseLocalData localData, TranslateStack stack, List<GraphTargetItem> output, int staticOperation, String path) throws InterruptedException {
AVM2LocalData aLocalData = (AVM2LocalData) localData;
//int expectedSize = stack.size() - getStackPopCount(localData, stack) + getStackPushCount(localData, stack);
definition.translate(aLocalData, stack, this, output, null);
/*if (stack.size() != expectedSize) {
throw new Error("HONFIKA stack size mismatch");
}*/
}
@Override
public int getStackPopCount(BaseLocalData localData, TranslateStack stack) {
AVM2LocalData aLocalData = (AVM2LocalData) localData;
return getStackPopCount(aLocalData);
}
@Override
public int getStackPushCount(BaseLocalData localData, TranslateStack stack) {
AVM2LocalData aLocalData = (AVM2LocalData) localData;
return getStackPushCount(aLocalData);
}
public int getStackPopCount(AVM2LocalData aLocalData) {
return definition.getStackPopCount(this, aLocalData.abc);
}
public int getStackPushCount(AVM2LocalData aLocalData) {
return definition.getStackPushCount(this, aLocalData.abc);
}
@Override
public boolean isJump() {
return definition instanceof JumpIns;
}
@Override
public boolean isBranch() {
return (definition instanceof IfTypeIns) || (definition instanceof LookupSwitchIns);
}
@Override
public boolean isExit() {
return (definition instanceof ReturnValueIns) || (definition instanceof ReturnVoidIns) || (definition instanceof ThrowIns);
}
@Override
public long getAddress() {
return address;
}
public void setAddress(long address) {
this.address = address;
}
public long getTargetAddress() {
return address + 4 /*getBytesLength()*/ + operands[0];
}
public void setTargetOffset(int offset) {
operands[0] = offset;
}
@Override
public List<Integer> getBranches(GraphSource code) {
List<Integer> ret = new ArrayList<>();
if (definition instanceof IfTypeIns) {
ret.add(code.adr2pos(getTargetAddress()));
if (!(definition instanceof JumpIns)) {
ret.add(code.adr2pos(address + getBytesLength()));
}
}
if (definition instanceof LookupSwitchIns) {
ret.add(code.adr2pos(address + operands[0]));
for (int k = 2; k < operands.length; k++) {
ret.add(code.adr2pos(address + operands[k]));
}
}
return ret;
}
@Override
public boolean ignoredLoops() {
return false;
}
@Override
public void setIgnored(boolean ignored, int pos) {
this.ignored = ignored;
}
@Override
public boolean isDeobfuscatePop() {
return definition instanceof DeobfuscatePopIns;
}
@Override
public AVM2Instruction clone() {
try {
AVM2Instruction ret = (AVM2Instruction) super.clone();
if (operands != null) {
ret.operands = Arrays.copyOf(operands, operands.length);
}
return ret;
} catch (CloneNotSupportedException ex) {
throw new RuntimeException();
}
}
@Override
public int getLine() {
return line;
}
@Override
public String getFile() {
return file;
}
/**
* Set operand value the right way - update offsets neccessarily. Because
* some operand types are variable length (like U30)
*
* @param operandIndex
* @param newValue
* @param code
* @param body
*/
public void setOperand(int operandIndex, int newValue, AVM2Code code, MethodBody body) {
int oldByteCount = getBytesLength();
operands[operandIndex] = newValue;
int newByteCount = getBytesLength();
int byteDelta = newByteCount - oldByteCount;
if (byteDelta != 0) {
code.updateInstructionByteCountByAddr(address, byteDelta, body);
}
body.setModified();
}
/**
* Set operand values the right way - update offsets neccessarily. Because
* some operand types are variable length (like U30)
*
* @param operands
* @param code
* @param body
*/
public void setOperands(int operands[], AVM2Code code, MethodBody body) {
int oldByteCount = getBytesLength();
this.operands = operands;
int newByteCount = getBytesLength();
int byteDelta = newByteCount - oldByteCount;
if (byteDelta != 0) {
code.updateInstructionByteCountByAddr(address, byteDelta, body);
}
body.setModified();
}
}