public class BitConfigInstruction extends AbstractInstruction {

	private BitConfigInfo config;
	private boolean constant;

	public BitConfigInstruction(InstructionInfo info, BitConfigInfo config, boolean constant) {
		super(info);
		this.config = config;
		this.constant = constant;
	}
	
	public BitConfigInfo getConfig() {
		return config;
	}

	public boolean getConstant() {
		return constant;
	}

	public String toString() {
		return super.toString() + "\t" + config + "\t" + constant;
	}

}
 public class BooleanInstruction extends AbstractInstruction {

	private boolean constant;

	public BooleanInstruction(InstructionInfo info, boolean constant) {
		super(info);
		this.constant = constant;
	}

	public boolean getConstant() {
		return constant;
	}

	public String toString() {
		return super.toString() + "\t" + constant;
	}

}
 public class ConfigInstruction extends AbstractInstruction {

	private ConfigInfo config;
	private boolean constant;

	public ConfigInstruction(InstructionInfo info, ConfigInfo config, boolean constant) {
		super(info);
		this.config = config;
		this.constant = constant;
	}
	
	public ConfigInfo getConfig() {
		return config;
	}

	public boolean getConstant() {
		return constant;
	}

	public String toString() {
		return super.toString() + "\t" + config + "\t" + constant;
	}

}
 public class IntInstruction extends AbstractInstruction {

	private int constant;

	public IntInstruction(InstructionInfo info, int constant) {
		super(info);
		this.constant = constant;
	}

	public int getConstant() {
		return constant;
	}

	public String toString() {
		return super.toString() + "\t" + constant;
	}

}
 public class JumpInstruction extends AbstractInstruction {

	private Label target;

	public JumpInstruction(InstructionInfo info, Label target) {
		super(info);
		this.target = target;
	}

	public Label getTarget() {
		return target;
	}

	public String toString() {
		return super.toString() + "\t" + target.toString();
	}

}
 public class Label extends AbstractInstruction {
	private int labelID;
	
	public Label() {
		super(null);
	}
	
	public int hashCode() {
		return labelID;
	}

	public String toString() {
		return "label_" + labelID + ":";
	}

	public void setLabelID(int labelID) {
		this.labelID = labelID;
	}

	public int getLabelID() {
		return labelID;
	}
}
 public class LongInstruction extends AbstractInstruction {

	private long constant;

	public LongInstruction(InstructionInfo info, long constant) {
		super(info);
		this.constant = constant;
	}

	public long getConstant() {
		return constant;
	}

	public String toString() {
		return super.toString() + "\t" + constant;
	}

}
 public class Opcodes {
	
	public static final int PUSH_INT = 0;
	public static final int PUSH_STR = 1;
	public static final int PUSH_LONG = 2;
	
	public static final int LOAD_INT = 3;
	public static final int STORE_INT = 4;
	public static final int LOAD_STR = 5;
	public static final int STORE_STR = 6;
	public static final int LOAD_LONG = 7;
	public static final int STORE_LONG = 8;
	
	public static final int POP_INT = 9;
	public static final int POP_STR = 10;
	public static final int POP_LONG = 11;
	
	public static final int NEW_ARRAY = 12;
	public static final int ARRAY_LOAD = 13;
	public static final int ARRAY_STORE = 14;
	
	public static final int CALL_CS2 = 15;
	public static final int RETURN = 16;
	
	public static final int SWITCH = 17;
	public static final int GOTO = 18;
	public static final int INT_EQ = 19;
	public static final int INT_NE = 20;
	public static final int INT_LT = 21;
	public static final int INT_GT = 22;
	public static final int INT_LE = 23;
	public static final int INT_GE = 24;
    public static final int INT_T = 25;
    public static final int INT_F = 26;
	public static final int LONG_EQ = 27;
	public static final int LONG_NE = 28;
	public static final int LONG_LT = 29;
	public static final int LONG_GT = 30;
	public static final int LONG_LE = 31;
	public static final int LONG_GE = 32;
	
	public static final int LOAD_CONFIG = 33;
	public static final int STORE_CONFIG = 34;
	public static final int LOAD_BITCONFIG = 35;
	public static final int STORE_BITCONFIG = 36;
	public static final int CONCAT_STRINGS = 37;
	
	
	public static int getOpcode(String name) {
		try {
			Field[] flds = Opcodes.class.getFields();
			for (Field f : flds) {
				if (!f.getName().equals(name)) {
					continue;
				}
				return f.getInt(null);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return -1;
	}

	public static String getOpcodeName(int opcode) {
		try {
			Field[] flds = Opcodes.class.getFields();
			for (Field f : flds) {
				if (f.getInt(null) != opcode) {
					continue;
				}
				return (f.getName());
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return "n/a:" + opcode;
	}
}
 public class StringInstruction extends AbstractInstruction {

	private String constant;

	public StringInstruction(InstructionInfo info, String constant) {
		super(info);
		this.constant = constant;
	}

	public String getConstant() {
		return constant;
	}

	public String toString() {
		return super.toString() + "\t" + constant;
	}

}
 public class SwitchInstruction extends AbstractInstruction {

	private int[] cases;
	private Label[] targets;
	private int defaultIndex;

	public SwitchInstruction(InstructionInfo info, int[] cases, Label[] targets) {
		super(info);
		this.cases = cases;
		this.targets = targets;
		this.defaultIndex = -1;
	}

	public int[] getCases() {
		return cases;
	}

	public Label[] getTargets() {
		return targets;
	}
	
	public int getDefaultIndex() {
		return defaultIndex;
	}
	
	
	public void attachDefault(Label default_) {
		Label[] nTargets = new Label[targets.length + 1];
		int[] nCases = new int[cases.length + 1];
		
		System.arraycopy(targets, 0, nTargets, 0, targets.length);
		System.arraycopy(cases, 0, nCases, 0, cases.length);
		
		targets = nTargets;
		cases = nCases;
		
		targets[targets.length - 1] = default_;
		cases[cases.length - 1] = Integer.MIN_VALUE;
		
		defaultIndex = targets.length - 1;
	}
	
	
	public void sort() {
		int[] sCases = new int[cases.length];
		Label[] sTargets = new Label[targets.length];
		boolean[] usage = new boolean[cases.length];
		boolean defaultAssigned = false;
		for (int sWrite = 0; sWrite < sCases.length; sWrite++) {
			int lowestAddr = Integer.MAX_VALUE;
			int lowestIndex = -1;
			for (int i = 0; i < cases.length; i++)
				if (!usage[i] && targets[i].getAddress() < lowestAddr)
					lowestAddr = targets[lowestIndex = i].getAddress();
			if (!defaultAssigned && defaultIndex == lowestIndex) {
				defaultAssigned = true;
				defaultIndex = sWrite;
			}
			usage[lowestIndex] = true;
			sCases[sWrite] = cases[lowestIndex];
			sTargets[sWrite] = targets[lowestIndex];
		}
		cases = sCases;
		targets = sTargets;
	}
	
	
	
	

	public String toString() {
		StringBuilder bld = new StringBuilder();
		bld.append("switch { \n");
		for (int i = 0; i < cases.length; i++) {
			bld.append(i == defaultIndex ? "\tdefault: \n" : "\tcase " + cases[i] + ": \n");
			bld.append("\t\t" + Opcodes.getOpcodeName(Opcodes.GOTO) + "\t" + targets[i].toString() + " \n");
		}
		bld.append("}");
		return bld.toString();
	}

}
 public class BitConfigInfo {
	
	private ConfigInfo base;
	private String name;
	
	public BitConfigInfo(ConfigInfo base, String name) {
		this.base = base;
		this.name = name;
	}

	public ConfigInfo getBase() {
		return base;
	}
	
	public String getName() {
		return name;
	}
	
	@Override
	public String toString() {
		return "[" + base + "," + name + "]";
	}
	
}
 public class ByteBuffer {

	private int position;

	private byte[] buffer;

	private static char[] unicodeTable = {'\u20ac', '\0', '\u201a', '\u0192', '\u201e', '\u2026', '\u2020', '\u2021', '\u02c6', '\u2030', '\u0160', '\u2039', '\u0152', '\0', '\u017d', '\0', '\0', '\u2018', '\u2019', '\u201c', '\u201d', '\u2022', '\u2013', '\u2014', '\u02dc', '\u2122', '\u0161', '\u203a', '\u0153', '\0', '\u017e', '\u0178'};

	private static final int xteaDelta = 0x9E3779B9;

	private static final int xteaRounds = 32;

	public ByteBuffer() {
		this(new byte[5000], 0);
	}

	public ByteBuffer(byte[] data) {
		this(data, 0);
	}

	public ByteBuffer(int capacity) {
		this(new byte[capacity], 0);
	}

	public ByteBuffer(byte[] data, int offset) {
		this.buffer = data;
		this.position = offset;
	}

	public void setPosition(int position) {
		if (position < 0 || position >= this.buffer.length)
			throw new IllegalArgumentException();
		this.position = position;
	}
	
	public void setPositionTrusted(int position) {
		this.position = position;
	}

	public int getPosition() {
		return position;
	}

	public byte[] toArray(int offset, int length) {
		byte[] bf = new byte[length - offset];
		for (int i = 0; i < length; i++) {
			bf[i] = this.buffer[offset + i];
		}
		return bf;
	}

	public byte[] getBuffer() {
		return this.buffer;
	}

	public final byte readSignedByte() {
		return this.buffer[this.position++];
	}

	public final int readUByte() {
		return this.buffer[this.position++] & 0xff;
	}

	public final void writeByte(int v) {
		this.buffer[this.position++] = (byte) v;
	}

	public final void readBytes(byte[] buffer, int offset, int length) {
		for (int pos = offset; pos < length + offset; pos++)
			buffer[pos] = this.buffer[this.position++];
	}

	public final void writeBytes(byte[] buffer, int offset, int length) {
		for (int pos = offset; pos < offset + length; pos++)
			this.buffer[this.position++] = buffer[pos];
	}

	public final int readUnsignedSmart() {
		int v = this.buffer[this.position] & 0xff;
		if (v >= 0x80)
			return readUnsignedShort() - 0x8000;
		return readUByte();
	}

	public final int readSignedSmart() {
		int v = this.buffer[this.position] & 0xff;
		if (v < 0x80)
			return readUByte() - 0x40;
		return readUnsignedShort() - 0xC000;
	}

	public final void writeSmart(int v) {
		if (v >= 0 && v < 0x80)
			this.writeByte(v);
		else if (v >= 0 && v < 0x8000)
			this.writeShort(v + 0x8000);
		else
			throw new IllegalArgumentException();
	}

	public final int readSignedShort() {
		this.position += 2;
		int i = ((this.buffer[this.position - 1] & 0xff) + (this.buffer[this.position - 2] << 8 & 0xff00));
		if (i > 0x7FFF)
			i -= 0x10000;
		return i;
	}
	public final int readUnsignedShort() {
		this.position += 2;
		return ((this.buffer[this.position - 2] << 8 & 0xff00) + (this.buffer[this.position - 1] & 0xff));
	}

	public final void writeShort(int s) {
		this.buffer[this.position++] = (byte) (s >> 8);
		this.buffer[this.position++] = (byte) s;
	}

	public final int readUnsignedMedInt() {
		this.position += 3;
		return ((this.buffer[this.position - 2] << 8 & 0xff00) + ((this.buffer[this.position - 3] & 0xff) << 16) + (this.buffer[this.position - 1] & 0xff));
	}

	public final int readSignedMedInt() {
		this.position += 3;
		int v = ((this.buffer[this.position - 1] & 0xff) + (this.buffer[this.position - 2] << 8 & 0xff00) + (this.buffer[this.position - 3] << 16 & 0xff0000));
		if (v > 0x7FFFFF)
			v -= 0x1000000;
		return v;
	}

	public final void writeMedInt(int v) {
		this.buffer[this.position++] = (byte) (v >> 16);
		this.buffer[this.position++] = (byte) (v >> 8);
		this.buffer[this.position++] = (byte) v;
	}

	public final int readInt() {
		this.position += 4;
		return ((this.buffer[this.position - 1] & 0xff) + ((this.buffer[this.position - 3] & 0xff) << 16) + ((this.buffer[this.position - 4] << 24 & ~0xffffff) + (this.buffer[this.position - 2] << 8 & 0xff00)));
	}

	public final void writeInt(int value) {
		this.buffer[this.position++] = (byte) (value >> 24);
		this.buffer[this.position++] = (byte) (value >> 16);
		this.buffer[this.position++] = (byte) (value >> 8);
		this.buffer[this.position++] = (byte) value;
	}

	public final long read5Byte() {
		long v0 = (long) readUByte() & 0xffffffffL;
		long v1 = (long) readInt() & 0xffffffffL;
		return v1 + (v0 << 32);
	}

	public final long readLong() {
		long v0 = (long) readInt() & 0xffffffffL;
		long v1 = (long) readInt() & 0xffffffffL;
		return (v0 << 32) + v1;
	}

	public final void writeLong(long v) {
		this.buffer[this.position++] = (byte) (int) (v >> 56);
		this.buffer[this.position++] = (byte) (int) (v >> 48);
		this.buffer[this.position++] = (byte) (int) (v >> 40);
		this.buffer[this.position++] = (byte) (int) (v >> 32);
		this.buffer[this.position++] = (byte) (int) (v >> 24);
		this.buffer[this.position++] = (byte) (int) (v >> 16);
		this.buffer[this.position++] = (byte) (int) (v >> 8);
		this.buffer[this.position++] = (byte) (int) v;
	}
	
	public final int readSmart32() {
		if (buffer[position] >= 0) {
			return readUnsignedShort() & 0x7FFF;
		}
		return readInt() & 0x7FFFFFFF;
	}
	
	public final void writeSmart32(int v) {
		if (v < 0)
			throw new RuntimeException("Value too big.");
		if (v <= 0x7FFF)
			writeShort(v);
		else
			writeInt(v);
	}

	public final String readNullString() {
		if (this.buffer[this.position] == 0) {
			this.position++;
			return null;
		}
		return readString();
	}

	public final String readVersionedString() {
		return this.readVersionedString((byte) 0);
	}

	public final String readVersionedString(byte versionNumber) {
		byte vNumber = this.buffer[this.position++];
		if (vNumber != versionNumber)
			throw new IllegalStateException("Bad string version number!");
		int pos = this.position;
		while (this.buffer[this.position++] != 0) {
			/* empty */
		}
		int strLen = this.position - pos - 1;
		if (strLen == 0)
			return "";
		return decodeString(buffer, pos, strLen);
	}

	public final void writeVersionedString(String str) {
		this.writeVersionedString(str, (byte) 0);
	}

	public final void writeVersionedString(String str, byte version) {
		int nullIdx = str.indexOf('\0');
		if (nullIdx >= 0)
			throw new IllegalArgumentException("NUL character at " + nullIdx + "!");
		this.buffer[this.position++] = (byte) version;
		this.position += encodeString(buffer, this.position, str, 0, str.length());
		this.buffer[this.position++] = (byte) 0;
	}

	public final String readString() {
		int pos = this.position;
		while (this.buffer[this.position++] != 0) {
			/* empty */
		}
		int strlen = this.position - pos - 1;
		if (strlen == 0)
			return "";
		return decodeString(buffer, pos, strlen);
	}

	public final void writeString(String string) {
		int n = string.indexOf('\0');
		if (n >= 0)
			throw new IllegalArgumentException("NUL character at " + n + "!");
		this.position += encodeString(buffer, this.position, string, 0, string.length());
		this.buffer[this.position++] = (byte) 0;
	}

	public final int readSum() {
		int sum = 0;
		int incr = readUnsignedSmart();
		while (incr == 32767) {
			incr = readUnsignedSmart();
			sum += 32767;
		}
		sum += incr;
		return sum;
	}

	public final int readVarSeized() {
		int f = this.buffer[this.position++];
		int sum = 0;
		for (; f < 0; f = this.buffer[this.position++])
			sum = (sum | f & 0x7f) << 7;
		return sum | f;
	}

	public final void writeVarSeized(int val) {
		if ((val & ~0x7f) != 0) {
			if ((val & ~0x3fff) != 0) {
				if ((val & ~0x1fffff) != 0) {
					if ((val & ~0xfffffff) != 0)
						this.writeByte(val >>> 28 | 0x80);
					this.writeByte((val | 0x100a2c1c) >>> 21);
				}
				this.writeByte(val >>> 14 | 0x80);
			}
			this.writeByte((val | 0x4021) >>> 7);
		}
		this.writeByte(val & 0x7f);
	}

	public final long readDynamic(int numBytes) throws IllegalArgumentException {
		if (--numBytes < 0 || numBytes > 7)
			throw new IllegalArgumentException();
		long value = 0L;
		for (int bitsLeft = numBytes * 8; bitsLeft >= 0; bitsLeft -= 8)
			value |= ((long) this.buffer[this.position++] & 0xffL) << bitsLeft;
		return value;
	}

	public final void writeDynamic(int numBytes, long value) {
		if (--numBytes < 0 || numBytes > 7)
			throw new IllegalArgumentException();
		for (int bitsLeft = numBytes * 8; bitsLeft >= 0; bitsLeft -= 8)
			this.buffer[this.position++] = (byte) (int) (value >> bitsLeft);
	}

	public final void encryptXTEA(int[] keys, int offset, int length) {
		int originalPosition = this.position;
		this.position = offset;
		int numCycles = (length - offset) / 8;
		for (int cycle = 0; cycle < numCycles; cycle++) {
			int v0 = readInt();
			int v1 = readInt();
			int sum = 0;
			int numRounds = xteaRounds;
			while (numRounds-- > 0) {
				v0 += (sum + keys[sum & 0x3] ^ (v1 << 4 ^ v1 >>> 5) + v1);
				sum += xteaDelta;
				v1 += (sum + keys[(sum & 0x1d2f) >>> 11] ^ v0 + (v0 << 4 ^ v0 >>> 5));
			}
			this.position -= 8;
			writeInt(v0);
			writeInt(v1);
		}
		this.position = originalPosition;
	}

	public final void xteaDecrypt(int[] keys, int offset, int length) {
		int originalPosition = this.position;
		this.position = offset;
		int numCycles = (length - offset) / 8;
		for (int cycle = 0; cycle < numCycles; cycle++) {
			int v0 = readInt();
			int v1 = readInt();
			int numRounds = xteaRounds;
			int sum = xteaDelta * numRounds;
			while (numRounds-- > 0) {
				v1 -= (sum + keys[(sum & 0x196e) >>> 11] ^ (v0 << 4 ^ v0 >>> 5) + v0);
				sum -= xteaDelta;
				v0 -= keys[sum & 0x3] + sum ^ v1 + (v1 >>> 5 ^ v1 << 4);
			}
			this.position -= 8;
			this.writeInt(v0);
			this.writeInt(v1);
		}
		this.position = originalPosition;
	}

	static final String decodeString(byte[] buffer, int offset, int strLen) {
		char[] strBuffer = new char[strLen];
		int write = 0;
		for (int dc = 0; dc < strLen; dc++) {
			int data = buffer[dc + offset] & 0xff;
			if (data == 0)
				continue;
			if (data >= 128 && data < 160) {
				char uni = unicodeTable[data - 128];
				if (uni == 0)
					uni = '?';
				strBuffer[write++] = (char) uni;
				continue;
			}
			strBuffer[write++] = (char) data;
		}
		return new String(strBuffer, 0, write);
	}

	public static final int encodeString(byte[] buffer, int bufferOffset, String str, int strOffset, int strLen) {
		int charsToEncode = strLen - strOffset;
		for (int cc = 0; cc < charsToEncode; cc++) {
			char c = str.charAt(cc + strOffset);

			if ((c > 0 && c < 128) || (c >= 160 && c <= 255)) {
				buffer[bufferOffset + cc] = (byte) c;
				continue;
			}

			switch (c) {
				case '\u20ac' :
					buffer[bufferOffset + cc] = -128;
					break;
				case '\u201a' :
					buffer[bufferOffset + cc] = -126;
					break;
				case '\u0192' :
					buffer[bufferOffset + cc] = -125;
					break;
				case '\u201e' :
					buffer[bufferOffset + cc] = -124;
					break;
				case '\u2026' :
					buffer[bufferOffset + cc] = -123;
					break;
				case '\u2020' :
					buffer[bufferOffset + cc] = -122;
					break;
				case '\u2021' :
					buffer[bufferOffset + cc] = -121;
					break;
				case '\u02c6' :
					buffer[bufferOffset + cc] = -120;
					break;
				case '\u2030' :
					buffer[bufferOffset + cc] = -119;
					break;
				case '\u0160' :
					buffer[bufferOffset + cc] = -118;
					break;
				case '\u2039' :
					buffer[bufferOffset + cc] = -117;
					break;
				case '\u0152' :
					buffer[bufferOffset + cc] = -116;
					break;
				case '\u017d' :
					buffer[bufferOffset + cc] = -114;
					break;
				case '\u2018' :
					buffer[bufferOffset + cc] = -111;
					break;
				case '\u2019' :
					buffer[bufferOffset + cc] = -110;
					break;
				case '\u201c' :
					buffer[bufferOffset + cc] = -109;
					break;
				case '\u201d' :
					buffer[bufferOffset + cc] = -108;
					break;
				case '\u2022' :
					buffer[bufferOffset + cc] = -107;
					break;
				case '\u2013' :
					buffer[bufferOffset + cc] = -106;
					break;
				case '\u2014' :
					buffer[bufferOffset + cc] = -105;
					break;
				case '\u02dc' :
					buffer[bufferOffset + cc] = -104;
					break;
				case '\u2122' :
					buffer[bufferOffset + cc] = -103;
					break;
				case '\u0161' :
					buffer[bufferOffset + cc] = -102;
					break;
				case '\u203a' :
					buffer[bufferOffset + cc] = -101;
					break;
				case '\u0153' :
					buffer[bufferOffset + cc] = -100;
					break;
				case '\u017e' :
					buffer[bufferOffset + cc] = -98;
					break;
				case '\u0178' :
					buffer[bufferOffset + cc] = -97;
					break;
				default :
					buffer[bufferOffset + cc] = (byte) '?';
					break;
			}
		}
		return charsToEncode;
	}

}
 public class ConfigInfo {
	
	private String domainName;
	private String configName;
	private CS2Type type;
	
	public ConfigInfo(String domainName, String configName, CS2Type type) {
		this.domainName = domainName;
		this.configName = configName;
		this.type = type;
	}

	public String getDomainName() {
		return domainName;
	}
	
	public String getConfigName() {
		return configName;
	}
	
	public CS2Type getType() {
		return type;
	}
	
	@Override
	public String toString() {
		return "[" + domainName + "," + configName + "," + type + "]";
	}
	
}
 public class ConfigsDatabase {

	private File configsFile;
	private File bitConfigsFile;
	private ConfigInfo[][] configsInfo;
	private BitConfigInfo[] bitConfigsInfo;
	
	public ConfigsDatabase(File configsFile, File bitConfigsFile) {
		this.configsFile = configsFile;
		this.bitConfigsFile = bitConfigsFile;
		this.configsInfo = new ConfigInfo[100][40000];
		this.bitConfigsInfo = new BitConfigInfo[40000];
		this.readConfigsDatabase();
		this.readBitConfigsDatabase();
	}
	
	public ConfigsDatabase() {
		this.configsInfo = new ConfigInfo[100][40000];
		this.bitConfigsInfo = new BitConfigInfo[40000];
	}
	
	

	
	private void readConfigsDatabase() {
		try {
			BufferedReader reader = new BufferedReader(new FileReader(configsFile));
			int linesCount = 0;
			for (String line = reader.readLine(); line != null; line = reader.readLine(),linesCount++) {
				if (line.length() <= 0 || line.startsWith(" ") || line.startsWith("//") || line.startsWith("#"))
					continue;
				try {
					String[] split = line.split("\\ ");
					int domainType = Integer.parseInt(split[0]);
					int id = Integer.parseInt(split[1]);
					String domainName = split[2];
					String configName = split[3];
					String tstring = split[4];
					CS2Type type;
					if (!tstring.startsWith("."))
						type = CS2Type.forJagexChar(tstring.charAt(0));
					else
						type = CS2Type.forJagexChar((char)Short.parseShort(tstring.substring(1)));
					configsInfo[domainType][id] = new ConfigInfo(domainName, configName, type);
				}
				catch (Exception ex) {
					reader.close();
					throw new RuntimeException("Error parsing configs database file " + this.configsFile + " on line:" + (linesCount + 1), ex);
				}
			}
			reader.close();
		}
		catch (Throwable t) {
			throw new RuntimeException(t);
		}
	}
	
	private void readBitConfigsDatabase() {
		try {
			BufferedReader reader = new BufferedReader(new FileReader(bitConfigsFile));
			int linesCount = 0;
			for (String line = reader.readLine(); line != null; line = reader.readLine(),linesCount++) {
				if (line.length() <= 0 || line.startsWith(" ") || line.startsWith("//") || line.startsWith("#"))
					continue;
				try {
					String[] split = line.split("\\ ");
					int baseDomainId = Integer.parseInt(split[0]);
					int baseId = Integer.parseInt(split[1]);
					int id = Integer.parseInt(split[2]);
					String name = split[3];
					
					ConfigInfo base = getConfigInfo(baseDomainId, baseId);
					if (base == null)
						throw new RuntimeException("Can't find base config " + baseDomainId + "," + baseId);
					
					bitConfigsInfo[id] = new BitConfigInfo(base, name);
				}
				catch (Exception ex) {
					reader.close();
					throw new RuntimeException("Error parsing bitconfigs database file " + this.bitConfigsFile + " on line:" + (linesCount + 1), ex);
				}
			}
			reader.close();
		}
		catch (Throwable t) {
			throw new RuntimeException(t);
		}
	}
	
	
	
	public ConfigInfo getConfigInfo(int domainType, int id) {
		if (domainType < 0 || id < 0 || domainType >= configsInfo.length || id >= configsInfo[domainType].length)
			return null;
		return configsInfo[domainType][id];
	}
	
	public BitConfigInfo getBitConfigInfo(int id) {
		if (id < 0 || id >= bitConfigsInfo.length)
			return null;
		return bitConfigsInfo[id];
	}
	
	
	public int configsSize() {
		int total = 0;
		for (int i = 0; i < configsInfo.length; i++) {
			if (configsInfo[i] != null)
				total++;
		}
		return total;
	}
	
	public int bitConfigsSize() {
		int total = 0;
		for (int i = 0; i < bitConfigsInfo.length; i++) {
			if (bitConfigsInfo[i] != null)
				total++;
		}
		return total;
	}
	
}
 public class InstructionInfo {
	
	private String name;
	private int scrampledOpcode;
	private int originalOpcode;
	private boolean hasIntConstant;
	
	public InstructionInfo(String name, int scrampledop, int originalop, boolean hasIntConstant) {
		this.name = name;
		this.scrampledOpcode = scrampledop;
		this.originalOpcode = originalop;
		this.hasIntConstant = hasIntConstant;
	}
	
	public String getName() {
		return name;
	}

	public int getScrampledOpcode() {
		return scrampledOpcode;
	}

	public int getOpcode() {
		return originalOpcode;
	}

	public boolean hasIntConstant() {
		return hasIntConstant;
	}
	
	@Override
	public String toString() {
		return name + "[" + originalOpcode + "," + scrampledOpcode + ":" + hasIntConstant + "]";
	}
	
}
 public class InstructionsDatabase {

	private File file;
	private InstructionInfo[] info1;
	private InstructionInfo[] info2;
	
	public InstructionsDatabase(File file) {
		this.file = file;
		this.info1 = new InstructionInfo[40000];
		this.info2 = new InstructionInfo[40000];
		this.readDatabase();
	}
	
	public InstructionsDatabase() {
		this.info1 = new InstructionInfo[40000];
		this.info2 = new InstructionInfo[40000];
	}
	
	

	
	private void readDatabase() {
		try {
			BufferedReader reader = new BufferedReader(new FileReader(file));
			int linesCount = 0;
			for (String line = reader.readLine(); line != null; line = reader.readLine(),linesCount++) {
				if (line.length() <= 0 || line.startsWith(" ") || line.startsWith("//") || line.startsWith("#"))
					continue;
				try {
					String[] split = line.split("\\ ");
					String name = split[0];
					int scrampled = Integer.parseInt(split[1]);
					int unscrampled = Integer.parseInt(split[2]);
					boolean big = Boolean.parseBoolean(split[3]);
					if (info1[scrampled] != null || info2[unscrampled] != null)
						throw new RuntimeException("redefinition: " + scrampled + "," + unscrampled);
					info1[scrampled] = info2[unscrampled] = new InstructionInfo(name, scrampled, unscrampled, big);
				}
				catch (Exception ex) {
					ex.printStackTrace();
					reader.close();
					throw new RuntimeException("Error parsing instructions database file " + this.file + " on line:" + (linesCount + 1));
				}
			}
			reader.close();
		}
		catch (Throwable t) {
			throw new RuntimeException(t);
		}
	}
	
	
	
	public InstructionInfo getByScrampled(int opcode) {
		if (opcode < 0 || opcode >= info1.length)
			return null;
		return info1[opcode];
	}
	
	public InstructionInfo getByUnscrampled(int opcode) {
		if (opcode < 0 || opcode >= info2.length)
			return null;
		return info2[opcode];
	}
	
	
	public int size() {
		int total = 0;
		for (int i = 0; i < info1.length; i++) {
			if (info1[i] != null)
				total++;
		}
		return total;
	}
	
}

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import mgi.tools.jagdecs2.instructions.BitConfigInstruction;
import mgi.tools.jagdecs2.instructions.BooleanInstruction;
import mgi.tools.jagdecs2.instructions.ConfigInstruction;
import mgi.tools.jagdecs2.instructions.IntInstruction;
import mgi.tools.jagdecs2.instructions.JumpInstruction;
import mgi.tools.jagdecs2.instructions.Label;
import mgi.tools.jagdecs2.instructions.LongInstruction;
import mgi.tools.jagdecs2.instructions.Opcodes;
import mgi.tools.jagdecs2.instructions.StringInstruction;
import mgi.tools.jagdecs2.instructions.SwitchInstruction;
import mgi.tools.jagdecs2.util.BitConfigInfo;
import mgi.tools.jagdecs2.util.ByteBuffer;
import mgi.tools.jagdecs2.util.ConfigInfo;
import mgi.tools.jagdecs2.util.ConfigsDatabase;
import mgi.tools.jagdecs2.util.InstructionInfo;
import mgi.tools.jagdecs2.util.InstructionsDatabase;

/*
	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 mgi.tools.jagdecs2;



public class CS2Decoder {

	@SuppressWarnings({"unchecked", "rawtypes"})
	public static final CS2 readScript(InstructionsDatabase idb, ConfigsDatabase cdb, File file) throws IOException {
		if (!file.exists() || !file.isFile() || !file.canRead())
			throw new FileNotFoundException("Script file " + file + " does not exist.");
		FileInputStream stream = new FileInputStream(file);
		byte[] data = new byte[(int) file.length()];
		int readed = stream.read(data);
		stream.close();
		if (readed != data.length)
			throw new IOException("Reading failed.");
		ByteBuffer buffer = new ByteBuffer(data);

		buffer.setPosition(data.length - 2);
		int switchBlocksSize = buffer.readUnsignedShort();
		int codeBlockEnd = data.length - switchBlocksSize - 16 - 2;
		buffer.setPosition(codeBlockEnd);
		int codeSize = buffer.readInt();
		int intLocalsCount = buffer.readUnsignedShort();
		int stringLocalsCount = buffer.readUnsignedShort();
		int longLocalsCount = buffer.readUnsignedShort();

		int intArgsCount = buffer.readUnsignedShort();
		int stringArgsCount = buffer.readUnsignedShort();
		int longArgsCount = buffer.readUnsignedShort();

		int switchesCount = buffer.readUByte();
		Map[] switches = new HashMap[switchesCount];
		for (int i = 0; i < switchesCount; i++) {
			int numCases = buffer.readUnsignedShort();
			switches[i] = new HashMap<Integer, Integer>(numCases);
			while (numCases-- > 0) {
				switches[i].put(buffer.readInt(), buffer.readInt());
			}
		}
		buffer.setPosition(0);
		String scriptName = buffer.readNullString();
		int[] intPool = new int[codeSize];
		Object[] objectPool = new Object[codeSize];
		long[] longPool = new long[codeSize];
		InstructionInfo[] instructions = new InstructionInfo[codeSize];

		int writeOffset = 0;
		while (buffer.getPosition() < codeBlockEnd) {
			int opcode = buffer.readUnsignedShort();
			InstructionInfo info = idb.getByScrampled(opcode);
			if (info == null)
				throw new RuntimeException("Unknown opcode:" + opcode);
			opcode = info.getOpcode();			

			if (opcode == Opcodes.LOAD_CONFIG || opcode == Opcodes.STORE_CONFIG) {
				int domainType = buffer.readUByte();
				int id = buffer.readUnsignedShort();
				ConfigInfo cfg = cdb.getConfigInfo(domainType, id);
				if (cfg == null)
					throw new RuntimeException("Unknown config:" + domainType + "," + id);
				intPool[writeOffset] = buffer.readUByte();
				objectPool[writeOffset] = cfg;
			}
			else if (opcode == Opcodes.PUSH_STR) {
				int baseType = buffer.readUByte();
				if (baseType == 0) {
					info = idb.getByUnscrampled(opcode = Opcodes.PUSH_INT);
					intPool[writeOffset] = buffer.readInt();
				}
				else if (baseType == 1) {
					info = idb.getByUnscrampled(opcode = Opcodes.PUSH_LONG);
					longPool[writeOffset] = buffer.readLong();
				}
				else if (baseType == 2)
					objectPool[writeOffset] = buffer.readString();
				else
					throw new RuntimeException("Unknown base type:" + baseType);
			}
			else if (opcode == Opcodes.LOAD_BITCONFIG || opcode == Opcodes.STORE_BITCONFIG) {
				int id = buffer.readUnsignedShort();

				BitConfigInfo cfg = cdb.getBitConfigInfo(id);
