/* 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.nio.charset.Charset; import org.apache.log4j.Logger; public class VSMX { public static Logger log = Logger.getLogger("vsmx"); private static final int VSMX_SIGNATURE = 0x584D5356; // "VSMX" private static final int VSMX_VERSION = 0x00010000; private byte[] buffer; private int offset; private VSMXHeader header; private VSMXMem mem; private String name; public VSMXMem getMem() { return mem; } private int read8() { return buffer[offset++] & 0xFF; } private int read16() { return read8() | (read8() << 8); } private int read32() { return read16() | (read16() << 16); } private void read(byte[] buffer) { for (int i = 0; i < buffer.length; i++) { buffer[i] = (byte) read8(); } } private void seek(int offset) { this.offset = offset; } public VSMX(byte[] buffer, String name) { this.buffer = buffer; this.name = name; read(); } public String getName() { return name; } private void readHeader() { header = new VSMXHeader(); header.sig = read32(); header.ver = read32(); header.codeOffset = read32(); header.codeLength = read32(); header.textOffset = read32(); header.textLength = read32(); header.textEntries = read32(); header.propOffset = read32(); header.propLength = read32(); header.propEntries = read32(); header.namesOffset = read32(); header.namesLength = read32(); header.namesEntries = read32(); } private static boolean isZero(byte[] buffer, int offset, int length) { for (int i = 0; i < length; i++) { if (buffer[offset + i] != (byte) 0) { return false; } } return true; } private String[] readStrings(int stringsOffset, int length, int entries, Charset charset, int bytesPerChar) { String[] strings = new String[entries]; int stringIndex = 0; byte[] buffer = new byte[length]; seek(stringsOffset); read(buffer); int stringStart = 0; for (int i = 0; i < length; i += bytesPerChar) { if (isZero(buffer, i, bytesPerChar)) { String s = new String(buffer, stringStart, i - stringStart, charset); stringStart = i + bytesPerChar; strings[stringIndex++] = s; } } if (stringIndex != entries) { log.warn(String.format("readStrings: incorrect number of strings read: stringsOffset=0x%X, length=0x%X, entries=0x%X, bytesPerChar=%d, read entries=0x%X", stringsOffset, length, entries, bytesPerChar, stringIndex)); } return strings; } private void read() { readHeader(); if (header.sig != VSMX_SIGNATURE) { log.warn(String.format("Invalid VSMX signature 0x%08X", header.sig)); return; } if (header.ver != VSMX_VERSION) { log.warn(String.format("Invalid VSMX version 0x%08X", header.ver)); return; } if (header.codeOffset > header.size()) { log.warn(String.format("VSMX: skipping range after header: 0x%X-0x%X", header.size(), header.codeOffset)); seek(header.codeOffset); } if ((header.codeLength % VSMXGroup.SIZE_OF) != 0) { log.warn(String.format("VSMX: code length is not aligned to 8 bytes: 0x%X", header.codeLength)); } mem = new VSMXMem(); mem.codes = new VSMXGroup[header.codeLength / VSMXGroup.SIZE_OF]; for (int i = 0; i < mem.codes.length; i++) { mem.codes[i] = new VSMXGroup(); mem.codes[i].id = read32(); mem.codes[i].value = read32(); } mem.texts = readStrings(header.textOffset, header.textLength, header.textEntries, Charset.forName("UTF-16LE"), 2); mem.properties = readStrings(header.propOffset, header.propLength, header.propEntries, Charset.forName("UTF-16LE"), 2); mem.names = readStrings(header.namesOffset, header.namesLength, header.namesEntries, Charset.forName("ISO-8859-1"), 1); if (log.isDebugEnabled()) { debug(); } } public void debug() { for (int i = 0; i < mem.codes.length; i++) { StringBuilder s = new StringBuilder(); VSMXGroup code = mem.codes[i]; int opcode = code.id & 0xFF; if (opcode >= 0 && opcode < VSMXCode.VsmxDecOps.length) { s.append(VSMXCode.VsmxDecOps[opcode]); } else { s.append(String.format("UNKNOWN_%X", opcode)); } switch (opcode) { 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: case VSMXCode.VID_DEBUG_LINE: 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_VARIABLE: s.append(String.format(" %s", mem.names[code.value])); break; case VSMXCode.VID_PROPERTY: case VSMXCode.VID_METHOD: case VSMXCode.VID_SET_ATTR: case VSMXCode.VID_UNSET: case VSMXCode.VID_OBJ_ADD_ATTR: s.append(String.format(" %s", mem.properties[code.value])); break; case VSMXCode.VID_FUNCTION: int n = (code.id >> 16) & 0xFF; if (n != 0) { log.warn(String.format("Unexpected localvars value for function at line %d, expected 0, got %d", i, n)); } int args = (code.id >> 8) & 0xFF; int localVars = (code.id >> 24) & 0xFF; s.append(String.format(" args=%d, localVars=%d, startLine=%d", args, localVars, code.value)); break; case VSMXCode.VID_UNNAMED_VAR: s.append(String.format(" %d", code.value)); break; // jumps case VSMXCode.VID_JUMP: case VSMXCode.VID_JUMP_TRUE: case VSMXCode.VID_JUMP_FALSE: s.append(String.format(" line=%d", code.value)); break; // function calls case VSMXCode.VID_CALL_FUNC: case VSMXCode.VID_CALL_METHOD: case VSMXCode.VID_CALL_NEW: s.append(String.format(" args=%d", code.value)); break; case VSMXCode.VID_MAKE_FLOAT_ARRAY: s.append(String.format(" items=%d", code.value)); break; // ops w/o arg - check for zero case VSMXCode.VID_OPERATOR_ASSIGN: case VSMXCode.VID_OPERATOR_ADD: case VSMXCode.VID_OPERATOR_SUBTRACT: case VSMXCode.VID_OPERATOR_MULTIPLY: case VSMXCode.VID_OPERATOR_DIVIDE: case VSMXCode.VID_OPERATOR_MOD: case VSMXCode.VID_OPERATOR_POSITIVE: case VSMXCode.VID_OPERATOR_NEGATE: case VSMXCode.VID_OPERATOR_NOT: case VSMXCode.VID_P_INCREMENT: case VSMXCode.VID_P_DECREMENT: case VSMXCode.VID_INCREMENT: case VSMXCode.VID_DECREMENT: case VSMXCode.VID_OPERATOR_TYPEOF: case VSMXCode.VID_OPERATOR_EQUAL: case VSMXCode.VID_OPERATOR_NOT_EQUAL: case VSMXCode.VID_OPERATOR_IDENTITY: case VSMXCode.VID_OPERATOR_NON_IDENTITY: case VSMXCode.VID_OPERATOR_LT: case VSMXCode.VID_OPERATOR_LTE: case VSMXCode.VID_OPERATOR_GT: case VSMXCode.VID_OPERATOR_GTE: case VSMXCode.VID_OPERATOR_B_AND: case VSMXCode.VID_OPERATOR_B_XOR: case VSMXCode.VID_OPERATOR_B_OR: case VSMXCode.VID_OPERATOR_B_NOT: case VSMXCode.VID_OPERATOR_LSHIFT: case VSMXCode.VID_OPERATOR_RSHIFT: case VSMXCode.VID_OPERATOR_URSHIFT: case VSMXCode.VID_STACK_COPY: case VSMXCode.VID_STACK_SWAP: case VSMXCode.VID_END_STMT: case VSMXCode.VID_CONST_NULL: case VSMXCode.VID_CONST_EMPTYARRAY: case VSMXCode.VID_CONST_OBJECT: case VSMXCode.VID_ARRAY: case VSMXCode.VID_THIS: case VSMXCode.VID_ARRAY_INDEX: case VSMXCode.VID_ARRAY_INDEX_ASSIGN: case VSMXCode.VID_ARRAY_PUSH: case VSMXCode.VID_RETURN: case VSMXCode.VID_END: if (code.value != 0) { log.warn(String.format("Unexpected non-zero value at line #%d: 0x%X!", i, code.value)); } break; default: s.append(String.format(" 0x%X", code.value)); break; } log.debug(String.format("Line#%d: %s", i, s.toString())); } log.debug(decompile()); } private String decompile() { VSMXDecompiler decompiler = new VSMXDecompiler(this); return decompiler.toString(); } }