/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package gcb; import gcb.plugin.PluginManager; import java.io.BufferedReader; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.Inet6Address; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketTimeoutException; import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.LinkedList; import java.util.List; import java.util.Queue; import java.util.TimerTask; import java.util.zip.CRC32; /** * * @author wizardus */ public class GarenaInterface { public static int GARENA_MAIN = 0; public static int GARENA_ROOM = 1; public static int GARENA_PEER = 2; public static final String TIME_FORMAT = "HH:mm:ss"; //id in configuration public int id; //TODO change this to private //cryptography class GarenaEncrypt crypt; //login server address InetAddress main_address; //plugin manager PluginManager plugins; //login server objects Socket socket; DataOutputStream out; DataInputStream in; //room server objects Socket room_socket; DataOutputStream rout; DataInputStream rin; int room_id; //peer to peer objects int peer_port; DatagramSocket peer_socket; List<MemberInfo> members; List<RoomInfo> rooms; List<GarenaListener> listeners; //our user ID int user_id; //unknown values in myinfo block int unknown1; int unknown2; int unknown3; int unknown4; //external and internal IP address, will be set later byte[] iExternal; byte[] iInternal; //external and internal port, will be set later int pExternal; int pInternal; //whether external settings have been forced boolean externalForced = false; //myinfo block byte[] myinfo; //reverse host, to broadcast Garena client hosted games to LAN GCBReverseHost reverseHost; boolean reverseEnabled; //used for gcb_broadcastfilter_key private WC3Interface wc3i; //whether we're trying to shut down nicely boolean exitingNicely; //last time we received something from room, for idle checking long lastRoomReceivedTime = 0; //worker instance to handle peer packets PeerLoopWorker peerLoopWorker; //TCP connection pool manager GarenaTCPPool tcpPool; //bind address InetAddress bindAddress; public GarenaInterface(PluginManager plugins, int id) { this.id = id; this.plugins = plugins; crypt = new GarenaEncrypt(); members = new ArrayList<MemberInfo>(); rooms = new ArrayList<RoomInfo>(); listeners = new ArrayList<GarenaListener>(); exitingNicely = false; synchronized(Main.TIMER) { Main.TIMER.schedule(new HelloTask(), 1000, 10000); //send hello to all room peers every 10 seconds Main.TIMER.schedule(new PlayTask(), 1000, 60000); //this is used simply as a keep-alive Main.TIMER.schedule(new ExperienceTask(), 1000, 60000 * 15); //experience packet every 15 minutes } //configuration room_id = GCBConfig.configuration.getInt("garena" + id + "_roomid", 590633); peer_port = GCBConfig.configuration.getInt("garena" + id + "_peerport", 0); reverseEnabled = GCBConfig.configuration.getBoolean("gcb_reverse", false); //configuration: reconnect int reconnectInterval = GCBConfig.configuration.getInt("gcb_reconnect_interval", -1); if(reconnectInterval > 0) { //reconnect every now and then if desired synchronized(Main.TIMER) { Main.TIMER.schedule(new ReconnectTask(), 60000 * reconnectInterval, 60000 * reconnectInterval); } } //determine bind address from configuration bindAddress = null; String bindAddressString = GCBConfig.configuration.getString("gcb_bindaddress", null); try { if(bindAddressString != null && !bindAddressString.trim().equals("")) { bindAddress = InetAddress.getByName(bindAddressString); } } catch(IOException ioe) { Main.println(6, "[GInterface " + id + "] Unable to identify bind address: " + ioe.getLocalizedMessage()); } } public void clear() { synchronized(rooms) { rooms.clear(); } } public void setWC3Interface(WC3Interface wc3i) { this.wc3i = wc3i; } public WC3Interface getWC3Interface() { return wc3i; } public void setGarenaTCPPool(GarenaTCPPool tcpPool) { this.tcpPool = tcpPool; } public boolean init() { Main.println(5, "[GInterface " + id + "] Initializing..."); crypt.initAES(); crypt.initRSA(); //hostname lookup try { String main_hostname = GCBConfig.configuration.getString("garena" + id + "_mainhost", "con3.garenaconnect.com"); main_address = InetAddress.getByName(main_hostname); } catch(UnknownHostException uhe) { if(Main.DEBUG) { uhe.printStackTrace(); } Main.println(6, "[GInterface " + id + "] Unable to locate main host: " + uhe.getLocalizedMessage()); return false; } //connect Main.println(5, "[GInterface " + id + "] Connecting to " + main_address.getHostAddress() + "..."); try { socket = new Socket(); socket.bind(new InetSocketAddress(bindAddress, 0)); socket.connect(new InetSocketAddress(main_address, 7456), 10000); Main.println(7, "[GInterface " + id + "] Using local port: " + socket.getLocalPort()); } catch(IOException ioe) { if(Main.DEBUG) { ioe.printStackTrace(); } try { socket.close(); } catch(Exception e) {} socket = null; Main.println(6, "[GInterface " + id + "] Error while connecting to main host: " + ioe.getLocalizedMessage()); return false; } try { out = new DataOutputStream(socket.getOutputStream()); in = new DataInputStream(socket.getInputStream()); socket.setSoTimeout(10000); } catch(IOException ioe) { if(Main.DEBUG) { ioe.printStackTrace(); } Main.println(6, "[GInterface " + id + "] Error(2) while connecting to main host: " + ioe.getLocalizedMessage()); return false; } //try init reverse host if(reverseEnabled && reverseHost == null) { reverseHost = new GCBReverseHost(this); reverseHost.init(); reverseHost.start(); } return true; } public boolean initPeer() { Main.println(5, "[GInterface " + id + "] Initializing peer socket..."); //reload peer port in case it's set to 0 (autodetermine port) peer_port = GCBConfig.configuration.getInt("garena" + id + "_peerport", 0); //init GP2PP socket try { //if bindAddress unset, then use wildcard address; otherwise bind to specified address //similarly, if peer port is 0, allow OS to determine port if(bindAddress == null) { if(peer_port == 0) { peer_socket = new DatagramSocket(); peer_port = peer_socket.getLocalPort(); Main.println(7, "[GInterface " + id + "] Autoset peerport=" + peer_port); } else { peer_socket = new DatagramSocket(peer_port); } } else { if(peer_port == 0) { //in this case, we need to set a port since bind address is set //we'll just choose randomly, if the port is taken then GarenaReconnect should retry synchronized(Main.RANDOM) { peer_port = Main.RANDOM.nextInt(5000) + 1500; Main.println(7, "[GInterface " + id + "] Autoset peerport=" + peer_port); } } peer_socket = new DatagramSocket(peer_port, bindAddress); } if(peer_socket.getInetAddress() instanceof Inet6Address) { Main.println(7, "[GInterface " + id + "] Warning: binded to IPv6 address: " + peer_socket.getInetAddress()); } } catch(IOException ioe) { if(Main.DEBUG) { ioe.printStackTrace(); } Main.println(6, "[GInterface " + id + "] Unable to establish peer socket on port " + peer_port + ": " + ioe.getLocalizedMessage()); return false; } //start up peer loop worker, if not already if(peerLoopWorker == null) { peerLoopWorker = new PeerLoopWorker(); peerLoopWorker.start(); } //force set external address if desired String externalAddressString = GCBConfig.configuration.getString("gcb_externaladdress"); if(externalAddressString != null && !externalAddressString.trim().equals("")) { try { iExternal = InetAddress.getByName(externalAddressString).getAddress(); pExternal = peer_socket.getLocalPort(); externalForced = true; } catch(IOException ioe) { Main.println(6, "[GInterface " + id + "] Unable to identify external address: " + ioe.getLocalizedMessage()); } } return true; } public boolean initRoom() { Main.println(5, "[GInterface " + id + "] Connecting to room..."); //update room_id in case this is called from !room command room_id = GCBConfig.configuration.getInt("garena" + id + "_roomid", -1); String room_hostname = GCBConfig.configuration.getString("garena" + id + "_roomhost", null); //default server 9 //see if we should check by name instead if(room_id == -1 || room_hostname == null || room_hostname.trim().equals("")) { Main.println(7, "[GInterface " + id + "] Automatically searching for roomid and roomhost..."); String roomName = GCBConfig.configuration.getString("garena" + id + "_roomname", null); if(roomName == null) { Main.println(6, "[GInterface " + id + "] Error: no room name set; shutting down!"); return false; } File roomFile = new File("gcbrooms.txt"); if(!roomFile.exists()) { Main.println(6, "[GInterface " + id + "] Error: " + roomFile.getAbsolutePath() + " does not exist!"); return false; } //read file and hope there's name in it; don't be case sensitive, but some rooms repeat! try { BufferedReader in = new BufferedReader(new FileReader(roomFile)); String line; while((line = in.readLine()) != null) { String[] parts = line.split("\\*\\*"); if(parts[0].trim().equalsIgnoreCase(roomName)) { room_id = Integer.parseInt(parts[1]); room_hostname = parts[3]; Main.println(7, "[GInterface " + id + "] Autoset found match; name is [" + parts[0] + "]," + " id is [" + room_id + "]" + ", host is [" + room_hostname + "]," + " and game is [" + parts[5] + "]"); break; } } in.close(); if(room_id == -1 || room_hostname == null || room_hostname.trim().equals("")) { Main.println(6, "[GInterface " + id + "] Error: no matches found; exiting..."); return false; } } catch(IOException ioe) { if(Main.DEBUG) { ioe.printStackTrace(); } Main.println(6, "[GInterface " + id + "] Error during autosearch: " + ioe.getLocalizedMessage()); return false; } } InetAddress address = null; //hostname lookup Main.println(7, "[GInterface " + id + "] Conducting hostname lookup..."); try { address = InetAddress.getByName(room_hostname); } catch(UnknownHostException uhe) { if(Main.DEBUG) { uhe.printStackTrace(); } Main.println(6, "[GInterface " + id + "] Unable to locate room host: " + uhe.getLocalizedMessage()); return false; } //connect Main.println(5, "[GInterface " + id + "] Connecting to " + address.getHostAddress() + "..."); try { room_socket = new Socket(address, 8687, bindAddress, 0); Main.println(7, "[GInterface " + id + "] Using local port: " + room_socket.getLocalPort()); } catch(IOException ioe) { if(Main.DEBUG) { ioe.printStackTrace(); } Main.println(6, "[GInterface " + id + "] Error while connecting to room host: " + ioe.getLocalizedMessage()); return false; } try { rout = new DataOutputStream(room_socket.getOutputStream()); rin = new DataInputStream(room_socket.getInputStream()); } catch(IOException ioe) { if(Main.DEBUG) { ioe.printStackTrace(); } Main.println(6, "[GInterface " + id + "] Error(2) while connecting to room host: " + ioe.getLocalizedMessage()); return false; } //notify main server that we joined the room if(!sendGSPJoinedRoom(user_id, room_id)) { return false; } return true; } public void disconnectRoom() { Main.println(5, "[GInterface " + id + "] Disconnecting from room..."); //send GCRP part Main.println(7, "[GInterface " + id + "] Sending GCRP PART..."); ByteBuffer lbuf = ByteBuffer.allocate(9); lbuf.order(ByteOrder.LITTLE_ENDIAN); lbuf.putInt(5); //message size lbuf.put((byte) 0x23); //PART identifier lbuf.putInt(user_id); if(rout != null) { try { rout.write(lbuf.array()); } catch(IOException ioe) { //ignore } } if(room_socket != null) { try { room_socket.close(); } catch(IOException ioe) { //ignore } } //cleanup room objects synchronized(members) { members.clear(); } //notify the main server sendGSPJoinedRoom(user_id, 0); } public boolean sendGSPSessionInit() { Main.println(7, "[GInterface " + id + "] Sending GSP session init..."); ByteBuffer block = ByteBuffer.allocate(50); block.order(ByteOrder.LITTLE_ENDIAN); block.put(crypt.skey.getEncoded()); block.put(crypt.iv); block.putShort((short) 0xF00F); byte[] array = block.array(); ByteBuffer lbuf = null; try { byte[] encrypted = crypt.rsaEncryptPrivate(array); lbuf = ByteBuffer.allocate(encrypted.length + 6); lbuf.order(ByteOrder.LITTLE_ENDIAN); lbuf.putInt(258); lbuf.putShort((short) 0x00AD); lbuf.put(encrypted); } catch(Exception e) { Main.println(6, "[GInterface " + id + "] Encryption error in sendGSPSessionInit: " + e.getLocalizedMessage()); } try { out.write(lbuf.array()); return true; } catch(IOException ioe) { Main.println(6, "[GInterface " + id + "] I/O Error in sendGSPSessionInit: " + ioe.getLocalizedMessage()); return false; } } public boolean readGSPSessionInitReply() { Main.println(7, "[GInterface " + id + "] Reading GSP session init reply..."); try { byte[] size_bytes = new byte[3]; in.read(size_bytes); int size = GarenaEncrypt.byteArrayToIntLittleLength(size_bytes, 0, 3); //next byte should be 1 if(in.read() != 1) { Main.println(6, "[GInterface " + id + "] Warning in readGSPSessionInitReply: invalid data from Garena server"); } byte[] bb = new byte[size]; in.read(bb); byte[] data = crypt.aesDecrypt(bb); if(data[0] == -82 || data[0] == 174) { //-82 since byte is always signed and 174 is over max Main.println(6, "[GInterface " + id + "] GSP session init reply received!"); } else { Main.println(6, "[GInterface " + id + "] Warning: invalid type " + data[0] + " from Garena server"); } return true; } catch(IOException ioe) { Main.println(6, "[GInterface " + id + "] I/O Error in readGSPSessionInitReply: " + ioe.getLocalizedMessage()); return false; } catch(Exception e) { Main.println(6, "[GInterface " + id + "] Decryption error in readGSPSessionInitReply: " + e.getLocalizedMessage()); return false; } } public boolean sendGSPSessionHello() { Main.println(7, "[GInterface " + id + "] Sending GSP session hello..."); ByteBuffer block = ByteBuffer.allocate(7); block.order(ByteOrder.LITTLE_ENDIAN); block.put((byte) 0xD3); //hello packet identifier block.put((byte) 69); //language identifier; E block.put((byte) 78); //.....................N int version_identifier = GCBConfig.configuration.getInt("gcb_version", 0x0000027C); block.putInt(version_identifier); //version identifier byte[] array = block.array(); ByteBuffer lbuf = null; try { byte[] encrypted = crypt.aesEncrypt(array); lbuf = ByteBuffer.allocate(4 + encrypted.length); lbuf.order(ByteOrder.LITTLE_ENDIAN); lbuf.putShort((short) encrypted.length); lbuf.put((byte) 0); lbuf.put((byte) 1); lbuf.put(encrypted); } catch(Exception e) { Main.println(6, "[GInterface " + id + "] Encryption error in sendGSPSessionHello: " + e.getLocalizedMessage()); return false; } try { out.write(lbuf.array()); return true; } catch(IOException ioe) { Main.println(6, "[GInterface " + id + "] I/O Error in sendGSPSessionHello: " + ioe.getLocalizedMessage()); return false; } } public boolean readGSPSessionHelloReply() { Main.println(7, "[GInterface " + id + "] Reading GSP session hello reply..."); try { byte[] size_bytes = new byte[3]; in.read(size_bytes); int size = GarenaEncrypt.byteArrayToIntLittleLength(size_bytes, 0, 3); //next byte should be 1 if(in.read() != 1) { Main.println(6, "[GInterface " + id + "] Warning: invalid data from Garena server"); } byte[] bb = new byte[size]; in.read(bb); byte[] data = crypt.aesDecrypt(bb); if(data[0] == -45 || data[0] == 211) { Main.println(6, "[GInterface " + id + "] GSP session hello reply received!"); } else { Main.println(6, "[GInterface " + id + "] Warning: invalid type " + data[0] + " from Garena server"); } return true; } catch(IOException ioe) { Main.println(6, "[GInterface " + id + "] I/O Error in readGSPSessionHelloReply: " + ioe.getLocalizedMessage()); return false; } catch(Exception e) { Main.println(6, "[GInterface " + id + "] Decryption error in readGSPSessionHelloReply: " + e.getLocalizedMessage()); return false; } } public String getUsername() { if(GCBConfig.configuration.getString("garena_username") != null) { return GCBConfig.configuration.getString("garena_username"); } else if(GCBConfig.configuration.getString("garena" + id + "_username") != null) { return GCBConfig.configuration.getString("garena" + id + "_username"); } else { Main.println(6, "[GInterface " + id + "] Fatal error: username for this connection is not set."); System.exit(-1); return null; } } public String getPassword() { if(GCBConfig.configuration.getString("garena_password") != null) { return GCBConfig.configuration.getString("garena_password"); } else if(GCBConfig.configuration.getString("garena" + id + "_password") != null) { return GCBConfig.configuration.getString("garena" + id + "_password"); } else { Main.println(6, "[GInterface " + id + "] Fatal error: password for this connection is not set."); System.exit(-1); return null; } } public boolean sendGSPSessionLogin() { Main.println(7, "[GInterface " + id + "] Sending GSP session login..."); String username = getUsername(); String password = getPassword(); ByteBuffer block = ByteBuffer.allocate(69); block.order(ByteOrder.LITTLE_ENDIAN); block.put((byte) 0x1F); //packet identifier block.put((byte) 0); block.put((byte) 0); block.put((byte) 0); block.put((byte) 0); //now we need to put username try { byte[] username_bytes = username.getBytes("UTF-8"); if(username_bytes.length > 16) { Main.println(6, "[GInterface " + id + "] Fatal error: your username is much too long."); System.exit(-1); } byte[] username_buf = new byte[16]; System.arraycopy(username_bytes, 0, username_buf, 0, username_bytes.length); block.put(username_buf); } catch(UnsupportedEncodingException e) { Main.println(6, "[GInterface " + id + "] Fatal error in sendGSPSessionLogin: " + e.getLocalizedMessage()); System.exit(-1); } block.put((byte) 0); block.put((byte) 0); block.put((byte) 0); block.put((byte) 0); block.putInt(0x20); //32; size of password hash //now we need to hash password and send String password_hash = crypt.md5(password); try { byte[] password_bytes = password_hash.getBytes("UTF-8"); if(password_bytes.length > 33) { Main.println(6, "[GInterface " + id + "] Fatal error in sendGSPSessionLogin: password hash is much too long!"); System.exit(-1); } byte[] password_buf = new byte[33]; System.arraycopy(password_bytes, 0, password_buf, 0, password_bytes.length); block.put(password_buf); } catch(UnsupportedEncodingException e) { Main.println(6, "[GInterface " + id + "] Fatal error(2) in sendGSPSessionLogin: " + e.getLocalizedMessage()); System.exit(-1); } block.put((byte) 1); //now we need to put internal IP byte[] addr; if(bindAddress == null) { addr = GarenaEncrypt.internalAddress(); } else { addr = bindAddress.getAddress(); } block.put(addr); //external peer port block.order(ByteOrder.BIG_ENDIAN); block.putShort((short) peer_port); block.order(ByteOrder.LITTLE_ENDIAN); byte[] array = block.array(); ByteBuffer lbuf = null; try { byte[] encrypted = crypt.aesEncrypt(array); lbuf = ByteBuffer.allocate(4 + encrypted.length); lbuf.order(ByteOrder.LITTLE_ENDIAN); lbuf.putShort((short) encrypted.length); lbuf.put((byte) 0); lbuf.put((byte) 1); lbuf.put(encrypted); } catch(Exception e) { Main.println(6, "[GInterface " + id + "] Encryption error in sendGSPSessionLogin: " + e.getLocalizedMessage()); return false; } try { out.write(lbuf.array()); return true; } catch(IOException ioe) { Main.println(6, "[GInterface " + id + "] I/O Error in sendGSPSessionLogin: " + ioe.getLocalizedMessage()); return false; } } public boolean readGSPSessionLoginReply() { Main.println(7, "[GInterface " + id + "] Reading GSP session login reply..."); try { byte[] size_bytes = new byte[3]; in.read(size_bytes); int size = GarenaEncrypt.byteArrayToIntLittleLength(size_bytes, 0, 3); //next byte should be 1 if(in.read() != 1) { Main.println(6, "[GInterface " + id + "] Warning: invalid data from Garena server"); } byte[] bb = new byte[size]; in.read(bb); byte[] data = crypt.aesDecrypt(bb); if(data[0] == -187 || data[0] == 69) { Main.println(7, "[GInterface " + id + "] Successfully logged in!"); } else if(data[0] == -210 || data[0] == 46) { Main.println(6, "[GInterface " + id + "] Invalid username or password."); return false; } else { Main.println(6, "[GInterface " + id + "] Warning: invalid type " + data[0] + " from Garena server"); return false; } myinfo = new byte[data.length - 9]; System.arraycopy(data, 9, myinfo, 0, data.length - 9); processMyInfo(myinfo); return true; } catch(IOException ioe) { Main.println(6, "[GInterface " + id + "] I/O Error in readGSPSessionLoginReply: " + ioe.getLocalizedMessage()); return false; } catch(Exception e) { Main.println(6, "[GInterface " + id + "] Decryption error in readGSPSessionLoginReply: " + e.getLocalizedMessage()); return false; } } public void processMyInfo(byte[] array) { ByteBuffer buf = ByteBuffer.allocate(4096); buf.order(ByteOrder.LITTLE_ENDIAN); buf.put(array); user_id = buf.getInt(0); Main.println(8, "[GInterface " + id + "] Server says your ID is: " + user_id); byte[] str_bytes = new byte[16]; buf.position(4); buf.get(str_bytes); Main.println(8, "[GInterface " + id + "] Server says your username is: " + (GarenaEncrypt.strFromBytes(str_bytes))); str_bytes = new byte[2]; buf.position(20); buf.get(str_bytes); Main.println(8, "[GInterface " + id + "] Server says your country is: " + (GarenaEncrypt.strFromBytes(str_bytes))); unknown1 = buf.get(24); Main.println(8, "[GInterface " + id + "] Server says your experience is: " + GarenaEncrypt.unsignedByte(buf.get(25))); unknown2 = buf.get(26); /* get ports through lookup method int b1 = (0x000000FF & ((int)buf.get(40))); //make sure it's unsigned int b2 = (0x000000FF & ((int)buf.get(41))); pExternal = b1 << 8 | b2; Main.println("[GInterface " + id + "] Setting external peer port to " + pExternal); //22 0's b1 = (0x000000FF & ((int)buf.get(64))); b2 = (0x000000FF & ((int)buf.get(65))); pInternal = b1 << 8 | b2; Main.println("[GInterface " + id + "] Setting internal peer port to " + pInternal); */ //19 0's unknown3 = buf.get(85); unknown4 = buf.get(88); str_bytes = new byte[array.length - 92]; buf.position(92); buf.get(str_bytes); Main.println(8, "[GInterface " + id + "] Server says your email address is: " + (GarenaEncrypt.strFromBytes(str_bytes))); } public void readGSPLoop() { ByteBuffer lbuf = ByteBuffer.allocate(2048); while(true) { lbuf.clear(); lbuf.order(ByteOrder.LITTLE_ENDIAN); try { byte[] size_bytes = new byte[3]; in.readFully(size_bytes); int size = GarenaEncrypt.byteArrayToIntLittleLength(size_bytes, 0, 3); if(in.read() != 1) { Main.println(6, "[GInterface " + id + "] GSPLoop: warning: invalid data from Garena server"); } Main.println(10, "[GInterface " + id + "] GSPLoop: received " + size + " bytes of encrypted data"); byte[] bb = new byte[size]; in.readFully(bb); byte[] data = crypt.aesDecrypt(bb); //notify plugins plugins.onPacket(GARENA_MAIN, -1, data, 0, data.length); if(data[0] == 68) { processQueryResponse(data); } else { Main.println(10, "[GInterface " + id + "] GSPLoop: unknown type received: " + data[0]); } } catch(SocketTimeoutException ste) { //ignore, only matters on startup continue; } catch(IOException ioe) { Main.println(6, "[GInterface " + id + "] GSPLoop: error: " + ioe.getLocalizedMessage()); disconnected(GARENA_MAIN, true); return; } catch(Exception e) { if(Main.DEBUG) { e.printStackTrace(); } Main.println(6, "[GInterface " + id + "] GSPLoop: error: " + e.getLocalizedMessage()); } } } public void processQueryResponse(byte[] data) throws IOException { int id = GarenaEncrypt.byteArrayToIntLittle(data, 1); Main.println(5, "[GInterface " + id + "] Query response: user ID is " + id); } public boolean sendGSPQueryUser(String username) { Main.println(5, "[GInterface " + id + "] Querying by name: " + username); byte[] username_bytes = username.getBytes(); ByteBuffer block = ByteBuffer.allocate(username_bytes.length + 6); block.order(ByteOrder.LITTLE_ENDIAN); block.put((byte) 0x57); //query packet identifier block.putInt(username_bytes.length); //string size, excluding null byte block.put(username_bytes); block.put((byte) 0); //null byte; since getBytes does not add it automatically byte[] array = block.array(); ByteBuffer lbuf = null; try { byte[] encrypted = crypt.aesEncrypt(array); lbuf = ByteBuffer.allocate(4 + encrypted.length); lbuf.putShort((short) encrypted.length); lbuf.put((byte) 0); lbuf.put((byte) 1); lbuf.put(encrypted); } catch(Exception e) { Main.println(6, "[GInterface " + id + "] Encryption error in sendGSPQueryUser: " + e.getLocalizedMessage()); return false; } try { out.write(lbuf.array()); return true; } catch(IOException ioe) { Main.println(6, "[GInterface " + id + "] I/O error in sendGSPQueryUser: " + ioe.getLocalizedMessage()); disconnected(GARENA_MAIN, false); return false; } } //username is requester username, sent with friend request //message is the one sent with friend request that requested user will read public boolean sendGSPRequestFriend(int id, String username, String message) { Main.println(5, "[GInterface " + id + "] Friend requesting: " + id); byte[] username_bytes = username.getBytes(); byte[] message_bytes = message.getBytes(); ByteBuffer block = ByteBuffer.allocate(username_bytes.length + message_bytes.length + 19); block.order(ByteOrder.LITTLE_ENDIAN); block.put((byte) 0x48); //request packet identifier block.putInt(user_id); //requester user_id block.putInt(id); //requested user_id block.putInt(username_bytes.length); block.put(username_bytes); block.put((byte) 0); block.putInt(message_bytes.length); block.put(message_bytes); block.put((byte) 0); //null byte; since getBytes does not add it automatically byte[] array = block.array(); ByteBuffer lbuf = null; try { byte[] encrypted = crypt.aesEncrypt(array); lbuf = ByteBuffer.allocate(4 + encrypted.length); lbuf.order(ByteOrder.LITTLE_ENDIAN); lbuf.putShort((short) encrypted.length); lbuf.put((byte) 0); lbuf.put((byte) 1); lbuf.put(encrypted); } catch(Exception e) { Main.println(6, "[GInterface " + id + "] Encryption error in sendGSPRequestFriend: " + e.getLocalizedMessage()); return false; } try { if(out != null) out.write(lbuf.array()); return true; } catch(IOException ioe) { Main.println(6, "[GInterface " + id + "] I/O error in sendGSPRequestFriend: " + ioe.getLocalizedMessage()); disconnected(GARENA_MAIN, false); return false; } } //sends message so main server knows we joined a room public boolean sendGSPJoinedRoom(int userId, int roomId) { ByteBuffer block = ByteBuffer.allocate(9); block.order(ByteOrder.LITTLE_ENDIAN); block.put((byte) 0x52); //joined room identifier block.putInt(userId); //user ID block.putInt(roomId); //joined room ID byte[] array = block.array(); ByteBuffer lbuf = null; try { byte[] encrypted = crypt.aesEncrypt(array); lbuf = ByteBuffer.allocate(4 + encrypted.length); lbuf.order(ByteOrder.LITTLE_ENDIAN); lbuf.putShort((short) encrypted.length); lbuf.put((byte) 0); lbuf.put((byte) 1); lbuf.put(encrypted); } catch(Exception e) { Main.println(6, "[GInterface " + id + "] Encryption error in sendGSPJoinedRoom: " + e.getLocalizedMessage()); return false; } try { if(out != null) out.write(lbuf.array()); return true; } catch(IOException ioe) { Main.println(6, "[GInterface " + id + "] I/O error in sendGSPJoinedRoom: " + ioe.getLocalizedMessage()); return false; } } //only to be sent when connected to a room server //should be sent every 15 minutes if connected to a room public boolean sendGSPXP(int userId, int xpGain, int gameType) { ByteBuffer block = ByteBuffer.allocate(13); block.order(ByteOrder.LITTLE_ENDIAN); block.put((byte) 0x67); //GSP XP block.putInt(userId); //user ID block.putInt(xpGain); //xpGain = 50 basic, 100 gold, 200 premium, 300 platinum block.putInt(gameType); //game type byte[] array = block.array(); ByteBuffer lbuf = null; try { byte[] encrypted = crypt.aesEncrypt(array); lbuf = ByteBuffer.allocate(4 + encrypted.length); lbuf.order(ByteOrder.LITTLE_ENDIAN); lbuf.putShort((short) encrypted.length); lbuf.put((byte) 0); lbuf.put((byte) 1); lbuf.put(encrypted); } catch(Exception e) { Main.println(6, "[GInterface " + id + "] Encryption error in sendGSPXP: " + e.getLocalizedMessage()); return false; } try { if(out != null) out.write(lbuf.array()); return true; } catch(IOException ioe) { Main.println(6, "[GInterface " + id + "] I/O error in sendGSPXP: " + ioe.getLocalizedMessage()); disconnected(GARENA_MAIN, false); return false; } } public boolean sendGCRPMeJoin() { Main.println(7, "[GInterface " + id + "] Sending GCRP me join..."); //make sure we got myinfo from main server first if(myinfo == null) { Main.println(6, "[GInterface " + id + "] Unable to proceed: myinfo not received via GSP"); return false; } String password = getPassword(); String roomPassword = GCBConfig.configuration.getString("garena" + id + "_roompassword", ""); ByteBuffer buf = ByteBuffer.allocate(4096); buf.order(ByteOrder.LITTLE_ENDIAN); //fix myinfo //now we need to put external IP if(iExternal != null) { myinfo[28] = iExternal[0]; myinfo[29] = iExternal[1]; myinfo[30] = iExternal[2]; myinfo[31] = iExternal[3]; } //internal address iInternal = GarenaEncrypt.internalAddress(); myinfo[32] = iInternal[0]; myinfo[33] = iInternal[1]; myinfo[34] = iInternal[2]; myinfo[35] = iInternal[3]; //put external port, in big endian if(pExternal != 0) { byte[] port = GarenaEncrypt.shortToByteArray((short) pExternal); myinfo[40] = port[0]; myinfo[41] = port[1]; } //put internal port, in big endian byte[] port = GarenaEncrypt.shortToByteArray((short) room_socket.getPort()); myinfo[42] = (byte) port[0]; myinfo[43] = (byte) port[1]; //add myinfo byte[] deflated = GarenaEncrypt.deflate(myinfo); Main.println(7, "[GInterface " + id + "] deflated myinfo block from " + myinfo.length + " bytes to " + deflated.length + " bytes"); buf.putInt(deflated.length + 66); //message size buf.put((byte) 0x22); //JOIN message identifier buf.putInt(room_id); buf.putInt(1); buf.putInt(deflated.length + 4); //CRC and myinfo size //generate CRC32 CRC32 crc = new CRC32(); crc.update(myinfo); buf.putInt((int) crc.getValue()); buf.put(deflated); //begin suffix //first 15 bytes are for room password byte[] roomPasswordBytes = null; try { roomPasswordBytes = roomPassword.getBytes("UTF-8"); if(roomPasswordBytes.length > 15) { System.out.println("[GInterface " + id + "] Warning: cutting room password to 15 bytes"); } int len = Math.min(roomPasswordBytes.length, 15); buf.put(roomPasswordBytes, 0, len); if(len < 15) { //fill in zero bytes; room password section must be exactly 15 bytes byte[] remainder = new byte[15 - len]; buf.put(remainder); //values in byte arrays default to zero } } catch(UnsupportedEncodingException e) { Main.println(6, "[GInterface " + id + "] Error in sendGCRPMeJoin: " + e.getLocalizedMessage() + "; ignoring room password"); buf.putInt(0); buf.putInt(0); buf.putInt(0); buf.putShort((short) 0); buf.put((byte) 0); } //now we need to hash password and send String password_hash = crypt.md5(password); try { byte[] password_bytes = password_hash.getBytes("UTF-8"); if(password_bytes.length > 33) { Main.println(6, "[GInterface " + id + "] Fatal error in sendGCRPMeJoin: password hash is much too long!"); System.exit(-1); } byte[] password_buf = new byte[32]; System.arraycopy(password_bytes, 0, password_buf, 0, Math.min(password_buf.length, password_bytes.length)); buf.put(password_buf); } catch(UnsupportedEncodingException e) { Main.println(6, "[GInterface " + id + "] Fatal error in sendGCRPMeJoin: " + e.getLocalizedMessage()); System.exit(-1); } buf.putShort((short) 0); try { rout.write(buf.array(), buf.arrayOffset(), buf.position()); return true; } catch(IOException ioe) { Main.println(6, "[GInterface " + id + "] I/O error in sendGCRPMeJoin: " + ioe.getLocalizedMessage()); disconnected(GARENA_ROOM, false); return false; } } public void readGCRPLoop() { ByteBuffer lbuf = ByteBuffer.allocate(65536); while(true) { lbuf.clear(); lbuf.order(ByteOrder.LITTLE_ENDIAN); try { byte[] header = new byte[4]; rin.readFully(header); int size = GarenaEncrypt.byteArrayToIntLittleLength(header, 0, 3); int type = rin.read(); lastRoomReceivedTime = System.currentTimeMillis(); if(type == 48) { processAnnounce(size - 1, lbuf); } else if(type == 44) { processMemberList(size - 1, lbuf); } else if(type == 34) { //JOIN message MemberInfo added = readMemberInfo(size - 1, lbuf); Main.println(9, "[GInterface " + id + "] New member joined: " + added.username + " (" + added.country + ") with id " + added.userID); synchronized(listeners) { for(GarenaListener listener : listeners) { listener.playerJoined(this, added); } } } else if(type == 35) { processMemberLeave(size - 1, lbuf); } else if(type == 37) { processMemberTalk(size - 1, lbuf); } else if(type == 58) { processMemberStart(size - 1, lbuf); } else if(type == 57) { processMemberStop(size - 1, lbuf); } else if(type == 127) { processWhisper(size - 1, lbuf); } else if(type == 54) { int error_id = -1; if(size >= 2) error_id = rin.read(); //read remaining if(size > 2) { byte[] tmp = new byte[size - 2]; rin.read(tmp); } String error_string; switch(error_id) { case 0x07: error_string = "room full"; break; case 0x0A: error_string = "insufficient level"; break; default: error_string = "unknown"; } Main.println(6, "[GInterface " + id + "] Error received: id: " + error_id + "; means: " + error_string); disconnected(GARENA_ROOM, true); return; } else { if(type == -1) { disconnected(GARENA_ROOM, true); return; } Main.println(10, "[GInterface " + id + "] GCRPLoop: unknown type received: " + type + "; size is: " + size); //make sure we read it all anyway if(size < 1000000000 && size >= 2) { byte[] tmp = new byte[size - 1]; //we already read type rin.read(tmp); //notify plugins plugins.onPacket(GARENA_ROOM, type, tmp, 0, size - 1); } } } catch(IOException ioe) { if(Main.DEBUG) { ioe.printStackTrace(); } Main.println(6, "[GInterface " + id + "] GCRP loop IO error: " + ioe.getLocalizedMessage()); disconnected(GARENA_ROOM, true); return; } catch(Exception e) { if(Main.DEBUG) { e.printStackTrace(); } Main.println(6, "[GInterface " + id + "] GCRP loop error: " + e.getLocalizedMessage()); } } } public void processAnnounce(int packet_size, ByteBuffer lbuf) throws IOException { lbuf.clear(); lbuf.put((byte) rin.read()); lbuf.put((byte) rin.read()); lbuf.put((byte) rin.read()); lbuf.put((byte) rin.read()); int serverRoomId = lbuf.getInt(0); byte[] str_bytes = new byte[packet_size - 4]; rin.readFully(str_bytes); String welcome_str = GarenaEncrypt.strFromBytes16(str_bytes); Main.println(8, "[GInterface " + id + "] Server says: " + welcome_str + " (reports roomid=" + serverRoomId + ")"); } public void processMemberList(int packet_size, ByteBuffer lbuf) throws IOException { lbuf.clear(); //read 8 bytes byte[] tmp = new byte[8]; rin.readFully(tmp); lbuf.put(tmp); int cRoom_id = lbuf.getInt(0); if(cRoom_id != room_id) { Main.println(8, "[GInterface " + id + "] Server says room ID is " + cRoom_id + "; tried to join room " + room_id); } int num_members = lbuf.getInt(4); Main.println(8, "[GInterface " + id + "] There are " + num_members + " members in this room"); //in case we are reconnecting after a disconnect // we don't want to keep the old member list synchronized(members) { members.clear(); } for(int i = 0; i < num_members; i++) { readMemberInfo(64, lbuf); } int read_size = 8 + 64 * num_members; if(packet_size > read_size) { tmp = new byte[packet_size - read_size]; rin.read(tmp); } if(GCBConfig.configuration.getBoolean("gcb_display_members", false)) { displayMemberInfo(); } } public MemberInfo readMemberInfo(int size, ByteBuffer lbuf) throws IOException { lbuf.clear(); lbuf.order(ByteOrder.LITTLE_ENDIAN); MemberInfo member = new MemberInfo(); //read 64 bytes byte[] tmp = new byte[64]; rin.readFully(tmp); lbuf.put(tmp); member.userID = lbuf.getInt(0); //username string lbuf.position(4); byte[] username_bytes = new byte[16]; lbuf.get(username_bytes); member.username = GarenaEncrypt.strFromBytes(username_bytes); //country string lbuf.position(20); byte[] country_bytes = new byte[2]; lbuf.get(country_bytes); member.country = GarenaEncrypt.strFromBytes(country_bytes); member.experience = GarenaEncrypt.unsignedByte(lbuf.get(25)); member.playing = (lbuf.get(27)) == 1; //external IP lbuf.position(28); byte[] external_bytes = new byte[4]; lbuf.get(external_bytes); //IP is stored in big endian anyway try { member.externalIP = InetAddress.getByAddress(external_bytes); } catch(UnknownHostException e) { Main.println(6, "[GInterface " + id + "] Error in readMemberInfo: " + e.getLocalizedMessage()); return member; } //internal IP lbuf.position(28); byte[] internal_bytes = new byte[4]; lbuf.get(internal_bytes); //IP is stored in big endian anyway try { member.internalIP = InetAddress.getByAddress(internal_bytes); } catch(UnknownHostException e) { Main.println(6, "[GInterface " + id + "] Error in readMemberInfo: " + e.getLocalizedMessage()); return member; } //ports in big endian lbuf.order(ByteOrder.BIG_ENDIAN); member.externalPort = GarenaEncrypt.unsignedShort(lbuf.getShort(40)); member.internalPort = GarenaEncrypt.unsignedShort(lbuf.getShort(42)); member.virtualSuffix = GarenaEncrypt.unsignedByte(lbuf.get(44)); lbuf.order(ByteOrder.LITTLE_ENDIAN); member.inRoom = true; synchronized(members) { members.add(member); } //read remainder if(size > 64) { tmp = new byte[size - 64]; rin.read(tmp); } return member; } public void displayMemberInfo() throws IOException { FileWriter out = new FileWriter("room_users_" + id + ".txt"); synchronized(members) { for(MemberInfo member : members) { out.write("user id: " + member.userID); out.write("\tusername: " + member.username); out.write("\tcountry: " + member.country); out.write("\texperience: " + member.experience); out.write("\tplaying?: " + member.playing); out.write("\texternal IP: " + member.externalIP); out.write("\tinternal IP: " + member.internalIP); out.write("\texternal port: " + member.externalPort); out.write("\tinternal port: " + member.internalPort); out.write("\tcorrect IP: " + member.correctIP); out.write("\tcorrect port: " + member.correctPort); out.write("\tvirtual suffix: " + member.virtualSuffix); out.write("\n"); } } out.close(); } public void displayRoomInfo() throws IOException { FileWriter out = new FileWriter("rooms.txt"); synchronized(rooms) { for(RoomInfo room : rooms) { out.write("room id: " + room.roomId); out.write("\t# users: " + room.numUsers); out.write("\n"); } } out.close(); } public void processMemberLeave(int packet_size, ByteBuffer lbuf) throws IOException { lbuf.clear(); lbuf.order(ByteOrder.LITTLE_ENDIAN); byte[] tmp = new byte[packet_size]; //should be 4 rin.read(tmp); lbuf.put(tmp); int user_id = lbuf.getInt(0); MemberInfo member = null; synchronized(members) { for(int i = 0; i < members.size(); i++) { if(members.get(i).userID == user_id) { member = members.remove(i); } } } if(member != null) { Main.println(9, "[GInterface " + id + "] " + member.username + " with ID " + member.userID + " has left the room"); } else { Main.println(9, "[GInterface " + id + "] Unlisted member " + user_id + " has left the room"); } synchronized(listeners) { for(GarenaListener listener : listeners) { listener.playerLeft(this, member); } } } public void processMemberStart(int packet_size, ByteBuffer lbuf) throws IOException { lbuf.clear(); lbuf.order(ByteOrder.LITTLE_ENDIAN); byte[] tmp = new byte[packet_size]; //should be 4 rin.read(tmp); lbuf.put(tmp); int user_id = lbuf.getInt(0); MemberInfo member = memberFromID(user_id); if(member != null) { member.playing = true; Main.println(9, "[GInterface " + id + "] " + member.username + " with ID " + member.userID + " has started playing"); } else { Main.println(9, "[GInterface " + id + "] Unlisted member " + user_id + " has started playing"); } synchronized(listeners) { for(GarenaListener listener : listeners) { listener.playerStarted(this, member); } } } public void processMemberStop(int packet_size, ByteBuffer lbuf) throws IOException { lbuf.clear(); lbuf.order(ByteOrder.LITTLE_ENDIAN); byte[] tmp = new byte[packet_size]; rin.read(tmp); lbuf.put(tmp); int user_id = lbuf.getInt(0); MemberInfo member = memberFromID(user_id); if(member != null) { member.playing = false; Main.println(9, "[GInterface " + id + "] " + member.username + " with ID " + member.userID + " has stopped playing"); } else { Main.println(9, "[GInterface " + id + "] Unlisted member " + user_id + " has stopped playing"); } for(GarenaListener listener : listeners) { listener.playerStopped(this, member); } } public void processWhisper(int packet_size, ByteBuffer lbuf) throws IOException { lbuf.clear(); lbuf.order(ByteOrder.LITTLE_ENDIAN); byte[] tmp = new byte[packet_size]; rin.read(tmp); lbuf.put(tmp); int user_id = lbuf.getInt(0); MemberInfo member = memberFromID(user_id); lbuf.position(8); byte[] chat_bytes = new byte[packet_size - 8]; lbuf.get(chat_bytes); String chat_string = GarenaEncrypt.strFromBytes16(chat_bytes); if(member != null) { Main.println(9, "[GInterface " + id + "] " + member.username + " with ID " + member.userID + " whispers: " + chat_string); } else { Main.println(9, "[GInterface " + id + "] Unlisted member " + user_id + " whispers: " + chat_string); } synchronized(listeners) { for(GarenaListener listener : listeners) { listener.chatReceived(this, member, chat_string, true); } } } public void processMemberTalk(int packet_size, ByteBuffer lbuf) throws IOException { lbuf.clear(); lbuf.order(ByteOrder.LITTLE_ENDIAN); byte[] tmp = new byte[packet_size]; //should be 4 rin.read(tmp); lbuf.put(tmp); int cRoom_id = lbuf.getInt(0); if(cRoom_id != room_id) { Main.println(8, "[GInterface " + id + "] Server says room ID is " + cRoom_id + "; tried to join room " + room_id); } int user_id = lbuf.getInt(4); MemberInfo member = memberFromID(user_id); lbuf.position(12); byte[] chat_bytes = new byte[packet_size - 12]; lbuf.get(chat_bytes); String chat_string = GarenaEncrypt.strFromBytes16(chat_bytes); if(member != null) { Main.println(9, "[GInterface " + id + "] " + member.username + " with ID " + member.userID + ": " + chat_string); } else { Main.println(9, "[GInterface " + id + "] Unlisted member " + user_id + ": " + chat_string); } synchronized(listeners) { for(GarenaListener listener : listeners) { listener.chatReceived(this, member, chat_string, false); } } } public boolean sendGCRPChat(String text) { Main.println(5, "[GInterface " + id + "] Sending message: " + text); byte[] chat_bytes = null; try { chat_bytes = text.getBytes("UnicodeLittleUnmarked"); } catch(UnsupportedEncodingException e) { Main.println(6, "[GInterface " + id + "] Error in sendGCRPChat: " + e.getLocalizedMessage()); return false; } ByteBuffer lbuf = ByteBuffer.allocate(19 + chat_bytes.length); lbuf.order(ByteOrder.LITTLE_ENDIAN); lbuf.putInt(15 + chat_bytes.length); //message size lbuf.put((byte) 0x25); //chat type lbuf.putInt(room_id); lbuf.putInt(user_id); lbuf.putInt(chat_bytes.length); lbuf.put(chat_bytes); lbuf.putShort((short) 0); //null byte try { rout.write(lbuf.array()); return true; } catch(IOException ioe) { Main.println(6, "[GInterface " + id + "] Error in sendGCRPChat: " + ioe.getLocalizedMessage()); disconnected(GARENA_ROOM, false); return false; } } public boolean sendGCRPAnnounce(String text) { Main.println(5, "[GInterface " + id + "] Sending announce: " + text); byte[] chat_bytes = null; try { chat_bytes = text.getBytes("UnicodeLittleUnmarked"); } catch(UnsupportedEncodingException e) { Main.println(6, "[GInterface " + id + "] Error in sendGCRPAnnounce: " + e.getLocalizedMessage()); return false; } ByteBuffer lbuf = ByteBuffer.allocate(11 + chat_bytes.length); lbuf.order(ByteOrder.LITTLE_ENDIAN); lbuf.putInt(7 + chat_bytes.length); //message size lbuf.put((byte) 0x30); //annouce (welcome message) type lbuf.putInt(room_id); lbuf.put(chat_bytes); lbuf.putShort((short) 0); //null byte try { rout.write(lbuf.array()); return true; } catch(IOException ioe) { Main.println(6, "[GInterface " + id + "] Error in sendGCRPAnnounce: " + ioe.getLocalizedMessage()); disconnected(GARENA_ROOM, false); return false; } } public boolean ban(String username, int hours) { int seconds = hours * 3600; Main.println(5, "[GInterface " + id + "] Banning " + username + " for " + seconds + " seconds"); byte[] username_bytes = null; try { username_bytes = username.getBytes("UTF-8"); } catch(UnsupportedEncodingException e) { Main.println(6, "[GInterface " + id + "] Error in ban: " + e.getLocalizedMessage()); return false; } ByteBuffer lbuf = ByteBuffer.allocate(14 + username_bytes.length); lbuf.order(ByteOrder.LITTLE_ENDIAN); lbuf.putInt(10 + username_bytes.length); //message size lbuf.put((byte) 0x78); //ban message identifier lbuf.putInt(room_id); lbuf.put(username_bytes); lbuf.put((byte) 0); //null byte lbuf.putInt(seconds); try { rout.write(lbuf.array()); return true; } catch(IOException ioe) { Main.println(6, "[GInterface " + id + "] Error in ban: " + ioe.getLocalizedMessage()); disconnected(GARENA_ROOM, false); return false; } } public boolean unban(String username) { return ban(username, 0); } public boolean kick(MemberInfo member) { return kick(member, ""); } public boolean kick(MemberInfo member, String reason) { Main.println(5, "[GInterface " + id + "] Kicking " + member.username + " with user ID " + member.userID + "; reason: " + reason); byte[] reason_bytes = null; try { reason_bytes = reason.getBytes("UTF-8"); } catch(UnsupportedEncodingException e) { Main.println(6, "[GInterface " + id + "] Error in kick: " + e.getLocalizedMessage()); return false; } ByteBuffer lbuf = ByteBuffer.allocate(18 + reason_bytes.length); lbuf.order(ByteOrder.LITTLE_ENDIAN); lbuf.putInt(14 + reason_bytes.length); //message size lbuf.put((byte) 0x28); //kick message identifier lbuf.putInt(user_id); lbuf.putInt(member.userID); //reason lbuf.putInt(reason_bytes.length); //reason size, excluding null terminator lbuf.put(reason_bytes); lbuf.put((byte) 0); //null terminator for reason try { rout.write(lbuf.array()); return true; } catch(IOException ioe) { Main.println(6, "[GInterface " + id + "] Error in kick: " + ioe.getLocalizedMessage()); disconnected(GARENA_ROOM, false); return false; } } public void startPlaying() { Main.println(7, "[GInterface " + id + "] Sending GCRP START..."); ByteBuffer lbuf = ByteBuffer.allocate(9); lbuf.order(ByteOrder.LITTLE_ENDIAN); lbuf.putInt(5); //message size lbuf.put((byte) 0x3a); //GCRP START lbuf.putInt(user_id); try { rout.write(lbuf.array()); } catch(IOException ioe) { Main.println(6, "[GInterface " + id + "] Error in startPlaying: " + ioe.getLocalizedMessage()); disconnected(GARENA_ROOM, false); } } public void stopPlaying() { Main.println(5, "[GInterface " + id + "] Sending GCRP STOP..."); ByteBuffer lbuf = ByteBuffer.allocate(9); lbuf.order(ByteOrder.LITTLE_ENDIAN); lbuf.putInt(5); //message size lbuf.put((byte) 0x39); //GCRP START lbuf.putInt(user_id); try { rout.write(lbuf.array()); } catch(IOException ioe) { Main.println(6, "[GInterface " + id + "] Error in stopPlaying: " + ioe.getLocalizedMessage()); disconnected(GARENA_ROOM, false); } } public boolean sendGCRPWhisper(int target_user, String text) { Main.println(5, "[GInterface " + id + "] Sending whisper to " + target_user + ": " + text); byte[] chat_bytes = null; try { chat_bytes = text.getBytes("UnicodeLittleUnmarked"); } catch(UnsupportedEncodingException e) { Main.println(6, "[GInterface " + id + "] Error in sendGCRPWhisper: " + e.getLocalizedMessage()); return false; } ByteBuffer lbuf = ByteBuffer.allocate(15 + chat_bytes.length); lbuf.order(ByteOrder.LITTLE_ENDIAN); lbuf.putInt(11 + chat_bytes.length); //message size lbuf.put((byte) 0x7F); //whisper lbuf.putInt(user_id); lbuf.putInt(target_user); lbuf.put(chat_bytes); lbuf.putShort((short) 0); //null byte try { rout.write(lbuf.array()); return true; } catch(IOException ioe) { Main.println(6, "[GInterface " + id + "] Error in sendGCRPWhisper: " + ioe.getLocalizedMessage()); disconnected(GARENA_ROOM, false); return false; } } public void registerListener(GarenaListener listener) { synchronized(listeners) { listeners.add(listener); } } public void deregisterListener(GarenaListener listener) { synchronized(listeners) { listeners.remove(listener); } } public void readPeerLoop() { byte[] buf_array = new byte[65536]; while(true) { try { DatagramPacket packet = new DatagramPacket(buf_array, buf_array.length); peer_socket.receive(packet); //notify plugins plugins.onPacket(GARENA_PEER, -1, buf_array, packet.getOffset(), packet.getLength()); int length = packet.getLength(); if(length == 0) { continue; } byte[] bytes = new byte[length]; System.arraycopy(buf_array, 0, bytes, 0, length); if(buf_array[0] == 0x06 || buf_array[0] == 0x3F || buf_array[0] == 0x0F || buf_array[0] == 0x02 || buf_array[0] == 0x01) { peerLoopWorker.append(packet.getAddress(), packet.getPort(), bytes); } else if(buf_array[0] == 0x0B && !exitingNicely) { //initconn, don't accept if we're exiting though tcpPool.enqueue(this, packet.getAddress(), packet.getPort(), bytes); } else if(buf_array[0] == 0x0D) { tcpPool.enqueue(this, packet.getAddress(), packet.getPort(), bytes); } else { Main.println(10, "[GInterface " + id + "] PeerLoop: unknown type received: " + buf_array[0] + "; size is: " + length); } } catch(IOException ioe) { Main.println(6, "[GInterface " + id + "] PeerLoop: error: " + ioe.getLocalizedMessage()); Main.println(6, "[GInterface " + id + "] PeerLoop: peer socket failed!"); ioe.printStackTrace(); disconnected(GARENA_PEER, true); } } } public void sendPeerLookup() { if(peer_socket == null) { Main.println(6, "[GInterface " + id + "] Failed to send lookup: peer socket not initialized"); return; } //lookup external IP, port byte[] tmp = new byte[8]; tmp[0] = 0x05; //we don't use peer_port because even if we're hosting Garena on 1515, server is still 1513 DatagramPacket packet = new DatagramPacket(tmp, tmp.length, main_address, 1513); try { peer_socket.send(packet); } catch(IOException ioe) { Main.println(6, "[GInterface " + id + "] Failed to send lookup: " + ioe.getLocalizedMessage()); ioe.printStackTrace(); } } public void sendPeerRoomUsage() { //lookup external IP, port ByteBuffer lbuf = ByteBuffer.allocate(5); lbuf.order(ByteOrder.LITTLE_ENDIAN); lbuf.put(0, (byte) 0x02); //room usage lookup identifier lbuf.putInt(1, user_id); //our user ID //we don't use peer_port because even if we're hosting Garena on 1515, server is still 1513 DatagramPacket packet = new DatagramPacket(lbuf.array(), lbuf.array().length, main_address, 1513); try { peer_socket.send(packet); } catch(IOException ioe) { Main.println(6, "[GInterface " + id + "] Failed to room usage check: " + ioe.getLocalizedMessage()); ioe.printStackTrace(); } } public void sendPeerHello() { synchronized(members) { for(MemberInfo target : members) { if(target.userID == user_id) { continue; } //LAN FIX: correct IP address of target user if(GCBConfig.configuration.getBoolean("gcb_lanfix", false)) { if(target.username.equalsIgnoreCase(GCBConfig.configuration.getString("gcb_lanfix_username", "garena"))) { try { target.correctIP = InetAddress.getByName(GCBConfig.configuration.getString("gcb_lanfix_ip", "192.168.1.2")); target.correctPort = GCBConfig.configuration.getInt("gcb_lanfix_port", 1513); } catch(IOException ioe) { Main.println(6, "[GInterface " + id + "] LAN FIX error: " + ioe.getLocalizedMessage()); ioe.printStackTrace(); } } } if(target.correctIP == null) { //send on both external and internal sendPeerHello(target.userID, target.externalIP, target.externalPort); sendPeerHello(target.userID, target.internalIP, target.internalPort); } else { sendPeerHello(target.userID, target.correctIP, target.correctPort); } } } //also send reverse SEARCH if needed if(reverseEnabled) { reverseHost.sendSearch(); } } public void sendPeerHello(int target_id, InetAddress address, int port) { ByteBuffer lbuf = ByteBuffer.allocate(16); lbuf.order(ByteOrder.LITTLE_ENDIAN); lbuf.put(0, (byte) 0x02); lbuf.putInt(4, user_id); DatagramPacket packet = new DatagramPacket(lbuf.array(), lbuf.array().length, address, port); try { peer_socket.send(packet); } catch(IOException ioe) { //ioe.printStackTrace(); //this happens a lot; ignore! } } public void sendPeerHelloReply(int target_id, InetAddress address, int port, ByteBuffer lbuf) { lbuf.clear(); lbuf.order(ByteOrder.LITTLE_ENDIAN); lbuf.put(0, (byte) 0x0F); lbuf.putInt(4, user_id); lbuf.putInt(12, target_id); lbuf.position(0); byte[] tmp = new byte[16]; lbuf.get(tmp); DatagramPacket packet = new DatagramPacket(tmp, tmp.length, address, port); try { peer_socket.send(packet); } catch(IOException ioe) { //ioe.printStackTrace(); //this happens a lot; ignore! } } public MemberInfo memberFromID(int id) { synchronized(members) { for(MemberInfo member : members) { if(member.userID == id) { return member; } } } return null; } public MemberInfo memberFromName(String name) { synchronized(members) { for(MemberInfo member : members) { if(member.username.equalsIgnoreCase(name)) { return member; } } } return null; } public void broadcastUDPEncap(int source, int destination, byte[] data, int offset, int length) { synchronized(members) { for(MemberInfo target : members) { if(target.userID == this.user_id) continue; if(!target.playing) continue; //don't broadcast if they don't have WC3 open if(target.correctIP == null) { //send on both external and internal sendUDPEncap(target.externalIP, target.externalPort, source, destination, data, offset, length); sendUDPEncap(target.internalIP, target.internalPort, source, destination, data, offset, length); } else { sendUDPEncap(target.correctIP, target.correctPort, source, destination, data, offset, length); } } } } public void sendUDPEncap(InetAddress address, int port, int source, int destination, byte[] data, int offset, int length) { ByteBuffer lbuf = ByteBuffer.allocate(length + 16); lbuf.order(ByteOrder.LITTLE_ENDIAN); lbuf.put(0, (byte) 0x01); lbuf.putInt(4, user_id); lbuf.order(ByteOrder.BIG_ENDIAN); lbuf.putShort(8, (short) source); lbuf.putShort(12, (short) destination); lbuf.position(16); lbuf.put(data, offset, length); byte[] array = lbuf.array(); DatagramPacket packet = new DatagramPacket(array, array.length, address, port); try { peer_socket.send(packet); } catch(IOException ioe) { //ioe.printStackTrace(); } } public GarenaTCP sendTCPInit(InetAddress address, int port, int targetPort, int remote_id, Socket socket) { //we create a new ByteBuffer because this is called from GCBReverseHost ByteBuffer tbuf = ByteBuffer.allocate(20); tbuf.order(ByteOrder.LITTLE_ENDIAN); tbuf.putInt(0x0b); tbuf.putInt(user_id); int conn_id = crypt.random.nextInt(); //generate random connection ID tbuf.putInt(conn_id); //put loopback address in big endian (for NAT compatibility?) try { byte[] loopbackBytes = InetAddress.getLocalHost().getAddress(); tbuf.put(loopbackBytes); } catch(UnknownHostException uhe) { Main.println(6, "[GInterface " + id + "] Failed to identify local host at sendTCPInit: " + uhe.getLocalizedMessage()); uhe.printStackTrace(); } tbuf.putShort((short) targetPort); //destination TCP port in LITTLE ENDIAN tbuf.putShort((short) 0); byte[] array = tbuf.array(); DatagramPacket packet = new DatagramPacket(array, array.length, address, port); try { peer_socket.send(packet); } catch(IOException ioe) { //ioe.printStackTrace(); } //init the GarenaTCP connection GarenaTCP tcp_connection = new GarenaTCP(this, null); tcp_connection.initReverse(address, port, remote_id, conn_id, socket); MemberInfo member = memberFromID(remote_id); if(member != null) { Main.println(5, "[GInterface " + id + "] Starting reverse TCP connection with " + member.username); } else { Main.println(5, "[GInterface " + id + "] Starting reverse TCP connection with " + remote_id); } tcpPool.registerConnection(conn_id, tcp_connection); return tcp_connection; } public void sendTCPData(InetAddress address, int port, int conn_id, long last_time, int seq, int ack, byte[] data, int len, ByteBuffer lbuf) { lbuf.position(0); lbuf.order(ByteOrder.LITTLE_ENDIAN); lbuf.put(0, (byte) 0x0D); //CONN message type identifier lbuf.put(1, (byte) 0x14); //CONN DATA message type identifier //long timestamp = (System.nanoTime() - last_time) / 256000; //lbuf.putShort(2, (short) timestamp); lbuf.putInt(4, conn_id); //connection ID lbuf.putInt(8, user_id); //sender user ID lbuf.putInt(12, seq); //SEQ number lbuf.putInt(16, ack); //ACK number lbuf.position(20); lbuf.put(data, 0, len); //payload byte[] array = lbuf.array(); DatagramPacket packet = new DatagramPacket(array, 0, len + 20, address, port); try { peer_socket.send(packet); } catch(IOException ioe) { //ioe.printStackTrace(); } } public void sendTCPAck(InetAddress address, int port, int conn_id, long last_time, int seq, int ack, ByteBuffer lbuf) { lbuf.position(0); lbuf.order(ByteOrder.LITTLE_ENDIAN); lbuf.put(0, (byte) 0x0D); //CONN message type identifier lbuf.put(1, (byte) 0x0E); //CONN ACK message type identifier //long timestamp = (System.nanoTime() - last_time) / 256000; //lbuf.putShort(2, (short) timestamp); lbuf.putInt(4, conn_id); //connection ID lbuf.putInt(8, user_id); //sender user ID lbuf.putInt(12, seq); //SEQ number lbuf.putInt(16, ack); //ACK number byte[] array = lbuf.array(); DatagramPacket packet = new DatagramPacket(array, 0, 20, address, port); try { peer_socket.send(packet); } catch(IOException ioe) { //ioe.printStackTrace(); } } public void sendTCPFin(InetAddress address, int port, int conn_id, long last_time, ByteBuffer lbuf) { lbuf.position(0); lbuf.order(ByteOrder.LITTLE_ENDIAN); lbuf.put(0, (byte) 0x0D); //CONN message type identifier lbuf.put(1, (byte) 0x01); //CONN FIN message type identifier //long timestamp = (System.nanoTime() - last_time) / 256000; //lbuf.putShort(2, (short) timestamp); lbuf.putInt(4, conn_id); //connection ID lbuf.putInt(8, user_id); //sender user ID byte[] array = lbuf.array(); DatagramPacket packet = new DatagramPacket(array, 0, 20, address, port); try { peer_socket.send(packet); //send 4 times to emulate client peer_socket.send(packet); peer_socket.send(packet); peer_socket.send(packet); } catch(IOException ioe) { //ioe.printStackTrace(); } } public static String time() { Calendar cal = Calendar.getInstance(); SimpleDateFormat sdf = new SimpleDateFormat(TIME_FORMAT); return sdf.format(cal.getTime()); } public void exitNicely() { exitingNicely = true; disconnected(GARENA_MAIN, false); disconnected(GARENA_ROOM, false); } public boolean isExiting() { return exitingNicely; } public int numRoomUsers() { synchronized(members) { return members.size(); } } public void disconnected(int x, boolean alert) { if(x == GARENA_MAIN && socket != null && socket.isConnected()) { try { socket.close(); } catch(IOException ioe) { //ignore } } else if(x == GARENA_ROOM && room_socket != null && room_socket.isConnected()) { try { room_socket.close(); } catch(IOException ioe) { //ignore } } else if(x == GARENA_PEER && peer_socket != null && peer_socket.isConnected()) { peer_socket.close(); } //only notify listeners if we're not exiting nicely and if this should alert //alert is set only if the disconnected call comes from one of the read loops // (otherwise, we never really connected, so no reason to call disconnected; // also this resolves problems where we get lots of threads from multiple // reconnection thread spawns) if(!exitingNicely && alert) { synchronized(listeners) { for(GarenaListener listener : listeners) { listener.disconnected(this, x); } } } plugins.onDisconnect(x); } class PeerLoopWorker extends Thread { //this class handles peer packets relating to the GarenaInterface (not relating to TCP connection) // we do this in a separate thread due to efficiency concerns Queue<PeerLoopWorkerPacket> packets; public PeerLoopWorker() { packets = new LinkedList<PeerLoopWorkerPacket>(); } public void append(InetAddress address, int port, byte[] bytes) { synchronized(packets) { packets.add(new PeerLoopWorkerPacket(address, port, bytes)); packets.notifyAll(); } } public void run() { ByteBuffer lbuf = ByteBuffer.allocate(65536); while(true) { try { PeerLoopWorkerPacket packet; synchronized(packets) { while(packets.isEmpty()) { try { packets.wait(); } catch(InterruptedException ie) {} } packet = packets.poll(); } lbuf.clear(); lbuf.put(packet.bytes); lbuf.order(ByteOrder.LITTLE_ENDIAN); if(packet.bytes[0] == 0x06) { if(!externalForced) { iExternal = new byte[4]; lbuf.position(8); lbuf.get(iExternal); lbuf.order(ByteOrder.BIG_ENDIAN); pExternal = GarenaEncrypt.unsignedShort(lbuf.getShort(12)); lbuf.order(ByteOrder.LITTLE_ENDIAN); String str_external = GarenaEncrypt.unsignedByte(iExternal[0]) + "." + GarenaEncrypt.unsignedByte(iExternal[1]) + "." + GarenaEncrypt.unsignedByte(iExternal[2]) + "." + GarenaEncrypt.unsignedByte(iExternal[3]); Main.println(7, "[GInterface " + id + "] PeerLoop: set address to " + str_external + " and port to " + pExternal); } } else if(packet.bytes[0] == 0x3F) { int room_prefix = GarenaEncrypt.unsignedShort(lbuf.getShort(1)); int num_rooms = GarenaEncrypt.unsignedByte(lbuf.get(3)); Main.println(7, "[GInterface " + id + "] Receiving " + num_rooms + " rooms with prefix " + room_prefix); for(int i = 0; i < num_rooms; i++) { RoomInfo room = new RoomInfo(); int suffix = GarenaEncrypt.unsignedByte(lbuf.get(4 + i * 2)); room.roomId = room_prefix * 256 + suffix; room.numUsers = GarenaEncrypt.unsignedByte(lbuf.get(5 + i * 2)); synchronized(rooms) { rooms.add(room); } } } else if(packet.bytes[0] == 0x0F) { int id = GarenaEncrypt.byteArrayToIntLittle(packet.bytes, 4); MemberInfo member = memberFromID(id); if(member != null) { member.correctIP = packet.address; member.correctPort = packet.port; } } else if(packet.bytes[0] == 0x02) { int id = GarenaEncrypt.byteArrayToIntLittle(packet.bytes, 4); MemberInfo member = memberFromID(id); if(member != null) { member.correctIP = packet.address; member.correctPort = packet.port; sendPeerHelloReply(member.userID, member.correctIP, member.correctPort, lbuf); } } else if(packet.bytes[0] == 0x01) { int senderId = lbuf.getInt(4); lbuf.order(ByteOrder.BIG_ENDIAN); lbuf.order(ByteOrder.LITTLE_ENDIAN); lbuf.position(16); //if we are using reverse, we simply forward the UDP packet to // the Warcraft client if(reverseEnabled) { reverseHost.receivedUDP(lbuf, packet.address, packet.port, senderId); } //otherwise, we want to check if this is a SEARCHGAME packet and // then send all of our cached GAMEINFO packets to the client // (depending on gcb configuration) else { wc3i.receivedUDP(GarenaInterface.this, lbuf, packet.address, packet.port, senderId); } } } catch(Exception e) { Main.println(1, "[GInterface " + id + "] CRITICAL ERROR: caught in loop:" + e.getLocalizedMessage()); System.err.println("[GInterface " + id + "] CRITICAL ERROR: caught in loop: " + e.getLocalizedMessage()); e.printStackTrace(); } } } class PeerLoopWorkerPacket { InetAddress address; int port; byte[] bytes; public PeerLoopWorkerPacket(InetAddress address, int port, byte[] bytes) { this.address = address; this.port = port; this.bytes = bytes; } } } class HelloTask extends TimerTask { public void run() { if(peer_socket != null && peer_socket.isBound()) { //in case of exception, don't print anything try { sendPeerHello(); } catch(Exception e) {} } } } class PlayTask extends TimerTask { public void run() { if(room_socket != null && room_socket.isConnected() && !isExiting()) { //in case of exception, don't print anything try { startPlaying(); } catch(Exception e) {} } } } class ExperienceTask extends TimerTask { public void run() { if(socket != null && socket.isConnected() && !isExiting()) { //in case of exception, don't print anything try { sendGSPXP(user_id, 100, 1001); } catch(Exception e) {} } } } class ReconnectTask extends TimerTask { public void run() { if(!GCBConfig.configuration.getBoolean("gcb_reconnect_idleonly", false) || System.currentTimeMillis() - lastRoomReceivedTime > 60000 * 10 || peer_socket == null || !peer_socket.isBound()) { //reconnect to Garena room Main.println(5, "[GInterface " + id + "] Reconnecting to Garena room"); disconnectRoom(); //room loop should take care of actual reconnection } } } }