/* This file is part of jpcsp. Jpcsp 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. Jpcsp 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 Jpcsp. If not, see <http://www.gnu.org/licenses/>. */ package jpcsp.format.rco.vsmx; import java.util.HashSet; import java.util.Set; import java.util.Stack; import org.apache.log4j.Logger; public class VSMXDecompiler { private static Logger log = VSMX.log; private VSMXMem mem; private String prefix; private Stack<Integer> blockEnd; private Stack<Integer> stack; private static final int SWITCH_STATE_NONE = 0; private static final int SWITCH_STATE_START = 1; private static final int SWITCH_STATE_VALUE = 2; private static final int SWITCH_STATE_CASE = 3; private static final int SWITCH_STATE_MULTI_VALUE = 4; private int switchState; private int switchBreakLine; private int ignoreFunctionSet; private int statementStartLine; private Set<Integer> needLineLabel; private StringBuilder booleanExpression; public VSMXDecompiler(VSMX vsmx) { mem = vsmx.getMem(); } private void increaseIndent(int blockEndLine) { prefix += " "; blockEnd.push(blockEndLine); } private void decrementIndent() { blockEnd.pop(); prefix = prefix.substring(0, prefix.length() - 2); } private void operator2(StringBuilder s, String operator) { StringBuilder op1 = new StringBuilder(); StringBuilder op2 = new StringBuilder(); decompileOp(op1); decompileOp(op2); s.append(String.format("%s%s%s", op2, operator, op1)); } private void operatorPre1(StringBuilder s, String operator) { StringBuilder op = new StringBuilder(); decompileOp(op); s.append(String.format("%s%s", operator, op)); } private void operatorPost1(StringBuilder s, String operator) { StringBuilder op = new StringBuilder(); decompileOp(op); s.append(String.format("%s%s", op, operator)); } private boolean isBooleanExpression(StringBuilder s) { if (stack.isEmpty()) { return false; } int i = stack.peek().intValue(); if (!mem.codes[i].isOpcode(VSMXCode.VID_STACK_COPY)) { return false; } stack.pop(); decompileOp(s); return true; } private void addToBooleanExpression(StringBuilder s) { if (booleanExpression == null) { booleanExpression = new StringBuilder(); } booleanExpression.append(s.toString()); } private void addToBooleanExpression(StringBuilder s, boolean isOr) { addToBooleanExpression(s); booleanExpression.append(isOr ? " || " : " && "); } private void decompileStmt(StringBuilder s) { if (stack.isEmpty()) { return; } int i = stack.pop(); decompileStmt(s, i); } private boolean detectSwitch(StringBuilder s, int i) { if (mem.codes[i + 1].isOpcode(VSMXCode.VID_DEBUG_LINE)) { i++; } if (!mem.codes[i + 1].isOpcode(VSMXCode.VID_JUMP)) { return false; } if (blockEnd.size() > 0 && blockEnd.peek().intValue() == i) { return false; } return true; } private boolean isSwitch(int jumpLine) { if (switchState == SWITCH_STATE_NONE) { return false; } if (switchState == SWITCH_STATE_CASE && switchBreakLine >= 0 && jumpLine != switchBreakLine) { return false; } return true; } private int decompileSwitch(StringBuilder s, int i, int jumpLine) { StringBuilder op; switch (switchState) { case SWITCH_STATE_NONE: op = new StringBuilder(); decompileOp(op); s.append(String.format("%sswitch (%s) {\n", prefix, op)); switchState = SWITCH_STATE_START; switchBreakLine = -1; break; case SWITCH_STATE_START: if (switchBreakLine >= 0 && jumpLine == switchBreakLine) { switchState = SWITCH_STATE_NONE; i = switchBreakLine - 1; } else { switchState = SWITCH_STATE_VALUE; } break; case SWITCH_STATE_VALUE: op = new StringBuilder(); decompileOp(op); if (mem.codes[i + 1].isOpcode(VSMXCode.VID_DEBUG_LINE)) { i++; } if (mem.codes[i + 1].isOpcode(VSMXCode.VID_JUMP)) { s.append(String.format("%scase %s:\n", prefix, op)); switchState = SWITCH_STATE_MULTI_VALUE; } else { s.append(String.format("%scase %s: {\n", prefix, op)); switchState = SWITCH_STATE_CASE; increaseIndent(0); } break; case SWITCH_STATE_MULTI_VALUE: switchState = SWITCH_STATE_VALUE; break; case SWITCH_STATE_CASE: s.append(String.format("%sbreak;\n", prefix)); decrementIndent(); s.append(String.format("%s}\n", prefix)); switchBreakLine = jumpLine; switchState = SWITCH_STATE_START; break; } return i; } private boolean isFunction(int i) { if (stack.isEmpty()) { return false; } int prev = stack.peek().intValue(); VSMXGroup prevCode = mem.codes[prev]; if (prevCode.isOpcode(VSMXCode.VID_OPERATOR_ASSIGN)) { prev--; prevCode = mem.codes[prev]; } if (!prevCode.isOpcode(VSMXCode.VID_FUNCTION) || prevCode.value != i + 1) { return false; } VSMXGroup prePrevCode = mem.codes[prev - 1]; if (prePrevCode.isOpcode(VSMXCode.VID_PROPERTY)) { prev--; prePrevCode = mem.codes[prev - 1]; } if (!prePrevCode.isOpcode(VSMXCode.VID_VARIABLE)) { return false; } return true; } private void decompileFunction(StringBuilder s, int setLine) { StringBuilder function = new StringBuilder(); decompileOp(function); StringBuilder name = new StringBuilder(); decompileOp(name); stack.push(setLine); StringBuilder set = new StringBuilder(); decompileOp(set); s.append(String.format("%s%s.%s = %s\n", prefix, name, set, function)); increaseIndent(setLine); ignoreFunctionSet = setLine; } private void decompileOp(StringBuilder s) { if (stack.isEmpty()) { return; } int i = stack.pop(); VSMXGroup code = mem.codes[i]; int opcode = code.getOpcode(); int args; StringBuilder ops[]; StringBuilder op, op1, op2, method; switch (opcode) { case VSMXCode.VID_VARIABLE: s.append(mem.names[code.value]); break; case VSMXCode.VID_UNNAMED_VAR: s.append(String.format("var%d", code.value)); break; case VSMXCode.VID_CONST_BOOL: if (code.value == 1) { s.append("true"); } else if (code.value == 0) { s.append("false"); } else { s.append(String.format("0x%X", code.value)); } break; case VSMXCode.VID_CONST_INT: s.append(String.format("%d", code.value)); break; case VSMXCode.VID_CONST_FLOAT: s.append(String.format("%f", code.getFloatValue())); break; case VSMXCode.VID_CONST_STRING: case VSMXCode.VID_DEBUG_FILE: s.append(String.format("\"%s\"", mem.texts[code.value])); break; case VSMXCode.VID_PROPERTY: op = new StringBuilder(); decompileOp(op); s.append(String.format("%s.%s", op, mem.properties[code.value])); break; case VSMXCode.VID_METHOD: case VSMXCode.VID_SET_ATTR: case VSMXCode.VID_UNSET: case VSMXCode.VID_OBJ_ADD_ATTR: s.append(mem.properties[code.value]); break; case VSMXCode.VID_FUNCTION: args = (code.id >> 8) & 0xFF; s.append("function("); for (int n = 0; n < args; n++) { if (n > 0) { s.append(", "); } s.append(String.format("var%d", n + 1)); } s.append(String.format(") {")); break; case VSMXCode.VID_CONST_EMPTYARRAY: s.append("{}"); break; case VSMXCode.VID_CONST_NULL: s.append("null"); break; case VSMXCode.VID_THIS: s.append("this"); break; case VSMXCode.VID_ARRAY_INDEX: op1 = new StringBuilder(); decompileOp(op1); op2 = new StringBuilder(); decompileOp(op2); s.append(String.format("%s[%s]", op2, op1)); break; case VSMXCode.VID_ARRAY_INDEX_KEEP_OBJ: op1 = new StringBuilder(); decompileOp(op1); i = stack.peek(); op2 = new StringBuilder(); decompileOp(op2); stack.push(i); s.append(String.format("%s[%s]", op2, op1)); break; case VSMXCode.VID_CALL_NEW: args = code.value; ops = new StringBuilder[args]; for (int n = args - 1; n >= 0; n--) { ops[n] = new StringBuilder(); decompileOp(ops[n]); } op = new StringBuilder(); decompileOp(op); s.append(String.format("new %s(", op)); for (int n = 0; n < args; n++) { if (n > 0) { s.append(", "); } s.append(ops[n]); } s.append(")"); break; case VSMXCode.VID_CALL_METHOD: args = code.value; ops = new StringBuilder[args]; for (int n = args - 1; n >= 0; n--) { ops[n] = new StringBuilder(); decompileOp(ops[n]); } method = new StringBuilder(); decompileOp(method); op = new StringBuilder(); decompileOp(op); s.append(String.format("%s.%s(", op, method)); for (int n = 0; n < args; n++) { if (n > 0) { s.append(", "); } s.append(ops[n]); } s.append(")"); break; case VSMXCode.VID_CALL_FUNC: args = code.value; ops = new StringBuilder[args]; for (int n = args - 1; n >= 0; n--) { ops[n] = new StringBuilder(); decompileOp(ops[n]); } method = new StringBuilder(); decompileOp(method); s.append(String.format("%s(", method)); for (int n = 0; n < args; n++) { if (n > 0) { s.append(", "); } s.append(ops[n]); } s.append(")"); break; case VSMXCode.VID_OPERATOR_EQUAL: operator2(s, " == "); break; case VSMXCode.VID_OPERATOR_NOT_EQUAL: operator2(s, " != "); break; case VSMXCode.VID_OPERATOR_GT: operator2(s, " > "); break; case VSMXCode.VID_OPERATOR_GTE: operator2(s, " >= "); break; case VSMXCode.VID_OPERATOR_LT: operator2(s, " < "); break; case VSMXCode.VID_OPERATOR_LTE: operator2(s, " <= "); break; case VSMXCode.VID_OPERATOR_NOT: operatorPre1(s, "!"); break; case VSMXCode.VID_OPERATOR_NEGATE: operatorPre1(s, "-"); break; case VSMXCode.VID_OPERATOR_ADD: operator2(s, " + "); break; case VSMXCode.VID_OPERATOR_SUBTRACT: operator2(s, " - "); break; case VSMXCode.VID_OPERATOR_MULTIPLY: operator2(s, " * "); break; case VSMXCode.VID_OPERATOR_DIVIDE: operator2(s, " / "); break; case VSMXCode.VID_OPERATOR_MOD: operator2(s, " % "); break; case VSMXCode.VID_OPERATOR_B_AND: operator2(s, " & "); break; case VSMXCode.VID_OPERATOR_B_XOR: operator2(s, " ^ "); break; case VSMXCode.VID_OPERATOR_B_OR: operator2(s, " | "); break; case VSMXCode.VID_OPERATOR_B_NOT: operatorPre1(s, "~"); break; case VSMXCode.VID_OPERATOR_LSHIFT: operator2(s, " << "); break; case VSMXCode.VID_OPERATOR_RSHIFT: operator2(s, " >> "); break; case VSMXCode.VID_OPERATOR_URSHIFT: operator2(s, " >>> "); break; case VSMXCode.VID_INCREMENT: operatorPost1(s, "++"); break; case VSMXCode.VID_DECREMENT: operatorPost1(s, "--"); break; case VSMXCode.VID_P_INCREMENT: operatorPre1(s, "++"); break; case VSMXCode.VID_P_DECREMENT: operatorPre1(s, "--"); break; case VSMXCode.VID_ARRAY_PUSH: op1 = new StringBuilder(); decompileOp(op1); if (!stack.isEmpty() && mem.codes[stack.peek().intValue()].isOpcode(VSMXCode.VID_ARRAY_PUSH)) { // Display nicely an array initialization while (!stack.isEmpty() && mem.codes[stack.peek().intValue()].isOpcode(VSMXCode.VID_ARRAY_PUSH)) { stack.pop(); op2 = new StringBuilder(); decompileOp(op2); op1.insert(0, String.format(",\n%s ", prefix)); op1.insert(0, op2.toString()); } op2 = new StringBuilder(); decompileOp(op2); s.append(String.format("%s {\n%s %s\n%s}", op2, prefix, op1, prefix)); } else { op2 = new StringBuilder(); decompileOp(op2); s.append(String.format("%s.push(%s)", op2, op1)); } break; case VSMXCode.VID_ARRAY: s.append("new Array()"); break; case VSMXCode.VID_OPERATOR_ASSIGN: op1 = new StringBuilder(); decompileOp(op1); op2 = new StringBuilder(); decompileOp(op2); s.append(String.format("%s = %s", op2, op1)); break; case VSMXCode.VID_STACK_COPY: if (!stack.isEmpty()) { i = stack.pop(); stack.push(i); stack.push(i); decompileOp(s); } break; case VSMXCode.VID_DEBUG_LINE: // Ignore debug line decompileOp(s); break; default: log.warn(String.format("Line #%d: decompileOp(%s) unimplemented", i, VSMXCode.VsmxDecOps[opcode])); break; } } private int decompileStmt(StringBuilder s, int i) { int initialLength = s.length(); VSMXGroup code = mem.codes[i]; int opcode = code.getOpcode(); StringBuilder op1; StringBuilder op2; StringBuilder op3; switch (opcode) { case VSMXCode.VID_OPERATOR_ASSIGN: op1 = new StringBuilder(); decompileOp(op1); op2 = new StringBuilder(); decompileOp(op2); s.append(String.format("%s%s = %s", prefix, op2, op1)); break; case VSMXCode.VID_ARRAY_INDEX_ASSIGN: op1 = new StringBuilder(); decompileOp(op1); op2 = new StringBuilder(); decompileOp(op2); op3 = new StringBuilder(); decompileOp(op3); s.append(String.format("%s%s[%s] = %s", prefix, op3, op2, op1)); break; case VSMXCode.VID_CALL_FUNC: case VSMXCode.VID_CALL_METHOD: stack.push(i); op1 = new StringBuilder(); decompileOp(op1); s.append(String.format("%s%s", prefix, op1)); break; case VSMXCode.VID_JUMP_TRUE: op1 = new StringBuilder(); if (isBooleanExpression(op1)) { addToBooleanExpression(op1, true); } else if (booleanExpression != null) { decompileOp(op1); addToBooleanExpression(op1, true); s.append(String.format("%sif (%s) {", prefix, booleanExpression)); increaseIndent(code.value); booleanExpression = null; } else { decompileOp(op1); // this is probably a "for" loop s.append(String.format("%d:\n", statementStartLine)); if (mem.codes[i + 1].isOpcode(VSMXCode.VID_JUMP)) { int elseGoto = mem.codes[i + 1].value; s.append(String.format("%sif (%s) goto %d; else goto %d", prefix, op1, code.value, elseGoto)); needLineLabel.add(elseGoto); needLineLabel.add(i + 2); i++; } else { s.append(String.format("%sif (%s) goto %d", prefix, op1, code.value)); } needLineLabel.add(code.value); } break; case VSMXCode.VID_JUMP_FALSE: op1 = new StringBuilder(); if (isBooleanExpression(op1)) { addToBooleanExpression(op1, false); } else if (booleanExpression != null) { decompileOp(op1); addToBooleanExpression(op1); s.append(String.format("%sif (%s) {", prefix, booleanExpression)); increaseIndent(code.value); booleanExpression = null; } else { decompileOp(op1); s.append(String.format("%sif (%s) {", prefix, op1)); increaseIndent(code.value); } break; case VSMXCode.VID_RETURN: op1 = new StringBuilder(); decompileOp(op1); s.append(String.format("%sreturn %s", prefix, op1)); break; case VSMXCode.VID_SET_ATTR: if (i == ignoreFunctionSet) { ignoreFunctionSet = -1; } else { op1 = new StringBuilder(); decompileOp(op1); op2 = new StringBuilder(); decompileOp(op2); s.append(String.format("%s%s.%s = %s", prefix, op2, mem.properties[code.value], op1)); } break; case VSMXCode.VID_INCREMENT: s.append(prefix); operatorPost1(s, "++"); break; case VSMXCode.VID_DECREMENT: s.append(prefix); operatorPost1(s, "--"); break; case VSMXCode.VID_P_INCREMENT: s.append(prefix); operatorPre1(s, "++"); break; case VSMXCode.VID_P_DECREMENT: s.append(prefix); operatorPre1(s, "--"); break; case VSMXCode.VID_OPERATOR_EQUAL: operator2(s, " == "); break; case VSMXCode.VID_JUMP: if (code.value > i) { increaseIndent(code.value); } else { // Backward loop s.append(String.format("%sgoto %d", prefix, code.value)); } break; case VSMXCode.VID_VARIABLE: s.append(prefix); s.append(mem.names[code.value]); break; case VSMXCode.VID_UNNAMED_VAR: s.append(prefix); s.append(String.format("var%d", code.value)); break; case VSMXCode.VID_DEBUG_LINE: break; default: log.warn(String.format("Line #%d: decompileStmt(%s) unimplemented", i, VSMXCode.VsmxDecOps[opcode])); break; } if (s.length() != initialLength) { if (s.charAt(s.length() - 1) != '{') { s.append(";"); } s.append(String.format(" // line %d", i)); s.append("\n"); } return i; } private String decompile() { StringBuilder s = new StringBuilder(); prefix = ""; stack = new Stack<Integer>(); blockEnd = new Stack<Integer>(); switchState = SWITCH_STATE_NONE; ignoreFunctionSet = -1; statementStartLine = 0; needLineLabel = new HashSet<Integer>(); for (int i = 0; i < mem.codes.length; i++) { VSMXGroup code = mem.codes[i]; int opcode = code.getOpcode(); while (!blockEnd.isEmpty() && blockEnd.peek().intValue() == i) { decrementIndent(); s.append(String.format("%s}\n", prefix)); } if (needLineLabel.remove(i)) { s.append(String.format("%d:\n", i)); } switch (opcode) { case VSMXCode.VID_END_STMT: decompileStmt(s); statementStartLine = i + 1; break; case VSMXCode.VID_RETURN: case VSMXCode.VID_JUMP_FALSE: case VSMXCode.VID_JUMP_TRUE: i = decompileStmt(s, i); break; case VSMXCode.VID_JUMP: if (isSwitch(code.value) || detectSwitch(s, i)) { i = decompileSwitch(s, i, code.value); } else if (isFunction(i)) { decompileFunction(s, code.value); } else { if (!blockEnd.isEmpty() && blockEnd.peek().intValue() == i + 1) { decrementIndent(); s.append(String.format("%s} else {\n", prefix)); } i = decompileStmt(s, i); } break; default: stack.push(i); break; } } return s.toString(); } @Override public String toString() { return decompile(); } }