Package net.bnubot.core.bncs

Source Code of net.bnubot.core.bncs.BNCSConnection

/**
* This file is distributed under the GPL
* $Id: BNCSConnection.java 529 2007-08-10 08:53:28Z scotta $
*/

package net.bnubot.core.bncs;

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.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedList;
import java.util.Random;

import net.bnubot.core.BNetInputStream;
import net.bnubot.core.BNetUser;
import net.bnubot.core.Connection;
import net.bnubot.core.ConnectionSettings;
import net.bnubot.core.CookieUtility;
import net.bnubot.core.StatString;
import net.bnubot.core.UnsupportedFeatureException;
import net.bnubot.core.clan.ClanMember;
import net.bnubot.core.clan.ClanRankIDs;
import net.bnubot.core.clan.ClanStatusIDs;
import net.bnubot.core.friend.FriendEntry;
import net.bnubot.core.queue.ChatQueue;
import net.bnubot.util.HexDump;
import net.bnubot.util.Out;
import net.bnubot.util.TimeFormatter;
import net.bnubot.vercheck.CurrentVersion;

import Hashing.*;

public class BNCSConnection extends Connection {
  public static final String[] clanRanks = {"Peon", "Grunt", "Shaman", "Chieftain"};
 
  protected Socket s = null;
  protected DataInputStream dis = null;
  protected DataOutputStream dos = null;
  private int productID = 0;
  private int verByte;
  private int nlsRevision = -1;
  private int serverToken = 0;
  private int clientToken = Math.abs(new Random().nextInt());
  private SRP srp = null;
  private byte proof_M2[] = null;
  private boolean forceReconnect = false;
  protected StatString myStatString = null;
  protected int myClan = 0;
  protected byte myClanRank = 0;
  protected long lastNullPacket;

  public BNCSConnection(ConnectionSettings cs, ChatQueue cq) {
    super(cs, cq);
  }
 
  private void sendPassword() throws Exception {
    if(nlsRevision == 0) {
      int passwordHash[] = DoubleHash.doubleHash(cs.password.toLowerCase(), clientToken, serverToken);
     
      BNCSPacket p = new BNCSPacket(BNCSCommandIDs.SID_LOGONRESPONSE2);
      p.writeDWord(clientToken);
      p.writeDWord(serverToken);
      p.writeDWord(passwordHash[0]);
      p.writeDWord(passwordHash[1]);
      p.writeDWord(passwordHash[2]);
      p.writeDWord(passwordHash[3]);
      p.writeDWord(passwordHash[4]);
      p.writeNTString(cs.username);
      p.SendPacket(dos, cs.packetLog);
     
    } else {
      srp = new SRP(cs.username, cs.password);
      srp.set_NLS(nlsRevision);
      byte A[] = srp.get_A();
     
      if(A.length != 32)
        throw new Exception("Invalid A length");
     
      BNCSPacket p = new BNCSPacket(BNCSCommandIDs.SID_AUTH_ACCOUNTLOGON);
      p.write(A);
      p.writeNTString(cs.username);
      p.SendPacket(dos, cs.packetLog);
    }
  }

  public void run() {
    while(true) {
      try {
        if(forceReconnect && (cs.isValid() == null))
          forceReconnect = false;
        else {
          if(!(cs.autoconnect && (cs.isValid() == null))) {
            while(!isConnected()) {
              yield();
              sleep(10);
            }
          }
        }
       
        setConnected(true);
        recieveInfo("Connecting to " + cs.bncsServer + ":" + cs.port);
        s = new Socket(cs.bncsServer, cs.port);
        dis = new DataInputStream(s.getInputStream());
        dos = new DataOutputStream(s.getOutputStream());
        nlsRevision = -1;
        productID = ProductIDs.ProductID[cs.product-1];
       
        //dos.write("GET / HTTP/1.0\n\n".getBytes());
       
        // Game
        dos.writeByte(0x01);
       
        verByte = HashMain.getVerByte(cs.product);
       
        BNCSPacket p;
       
        switch(cs.product) {
        case ConnectionSettings.PRODUCT_STARCRAFT:
        case ConnectionSettings.PRODUCT_BROODWAR:
        case ConnectionSettings.PRODUCT_DIABLO2:
        case ConnectionSettings.PRODUCT_LORDOFDESTRUCTION:
        case ConnectionSettings.PRODUCT_WARCRAFT3:
        case ConnectionSettings.PRODUCT_THEFROZENTHRONE:
        case ConnectionSettings.PRODUCT_WAR2BNE:
          p = new BNCSPacket(BNCSCommandIDs.SID_AUTH_INFO);
          p.writeDWord(0);              // Protocol ID (0)
          p.writeDWord(PlatformIDs.PLATFORM_IX86)// Platform ID (IX86)
          p.writeDWord(productID);          // Product ID
          p.writeDWord(verByte);            // Version byte
          p.writeDWord("enUS");            // Product language
          p.writeDWord(0);              // Local IP
          p.writeDWord(0xf0);              // TZ bias
          p.writeDWord(0x409);            // Locale ID
          p.writeDWord(0x409);            // Language ID
          p.writeNTString("USA");            // Country abreviation
          p.writeNTString("United States");      // Country
          p.SendPacket(dos, cs.packetLog);
          break;

        case ConnectionSettings.PRODUCT_STARCRAFTSHAREWARE:
        case ConnectionSettings.PRODUCT_JAPANSTARCRAFT:
          p = new BNCSPacket(BNCSCommandIDs.SID_CLIENTID);
          p.writeDWord(0)// Registration Version
          p.writeDWord(0)// Registration Authority
          p.writeDWord(0)// Account Number
          p.writeDWord(0)// Registration Token
          p.writeByte(0);    // LAN computer name
          p.writeByte(0);    // LAN username
          p.SendPacket(dos, cs.packetLog);
         
          p = new BNCSPacket(BNCSCommandIDs.SID_STARTVERSIONING);
          p.writeDWord(PlatformIDs.PLATFORM_IX86)// Platform ID (IX86)
          p.writeDWord(productID);          // Product ID
          p.writeDWord(verByte);            // Version byte
          p.writeDWord(0);              // Unknown (0)
          p.SendPacket(dos, cs.packetLog);
          break;
         
        /*case ConnectionSettings.PRODUCT_WAR2BNE:
          p = new BNCSPacket(BNCSCommandIDs.SID_CLIENTID2);
          p.writeDWord(1);  // Server version
          p.writeDWord(0);  // Registration Version
          p.writeDWord(0);  // Registration Authority
          p.writeDWord(0);  // Account Number
          p.writeDWord(0);  // Registration Token
          p.writeByte(0);    // LAN computer name
          p.writeByte(0);    // LAN username
          p.SendPacket(dos, cs.packetLog);
         
          p = new BNCSPacket(BNCSCommandIDs.SID_LOCALEINFO);
          p.writeQWord(0);    // System time
          p.writeQWord(0);    // Local time
          p.writeDWord(0xf0);    // TZ bias
          p.writeDWord(0x409);  // SystemDefaultLCID
          p.writeDWord(0x409);  // UserDefaultLCID
          p.writeDWord(0x409);  // UserDefaultLangID 
          p.writeNTString("ena");  // Abbreviated language name   
          p.writeNTString("1");  // Country code
          p.writeNTString("USA");  // Abbreviated country name
          p.writeNTString("United States");  // Country (English)
          p.SendPacket(dos, cs.packetLog);
         
          p = new BNCSPacket(BNCSCommandIDs.SID_STARTVERSIONING);
          p.writeDWord(PlatformIDs.PLATFORM_IX86);  // Platform ID (IX86)
          p.writeDWord(productID);          // Product ID
          p.writeDWord(verByte);            // Version byte
          p.writeDWord(0);              // Unknown (0)
          p.SendPacket(dos, cs.packetLog);
          break;*/
         
        default:
          recieveError("Don't know how to connect with product " + productID);
          setConnected(false);
          break;
        }
       
        //The connection loop
        connectedLoop();
         
        //Connection closed; do cleanup
        BNetUser.clearCache();
        myStatString = null;
        myUser = null;
        myClan = 0;
        myClanRank = 0;
        titleChanged();

        //Wait a short time before allowing a reconnect
      } catch(SocketException e) {
      } catch(Exception e) {
        recieveError("Unhandled exception: " + e.getMessage());
        e.printStackTrace();
      }

      setConnected(false);
      try { s.close(); } catch (Exception e) { }
      s = null;
      recieveError("Disconnected from battle.net.");
      yield();
      try { sleep(15000); } catch (InterruptedException e1) { }
    }
  }
 
  private static ArrayList<String> antiIdles = null;
  private String getAntiIdle() {
    if(antiIdles == null) {
      antiIdles = new ArrayList<String>();
      BufferedReader is = null;
      try {
        File f = new File("anti-idle.txt");
        if(!f.exists()) {
          f.createNewFile();
         
          FileWriter os = new FileWriter(f);
          os.write("# Enter anti-idle messages in this file.\r\n");
          os.write("# \r\n");
          os.write("# Lines beginning with '#' are regarded as comments\r\n");
          os.write("# \r\n");
          os.write("\r\n");
          os.close();
        }
        is = new BufferedReader(new FileReader(f));
      } catch (Exception e) {
        e.printStackTrace();
        System.exit(1);
      }
     
      do {
        String line = null;
        try {
          line = is.readLine();
        } catch (IOException e) {
          e.printStackTrace();
          System.exit(1);
        }
        if(line == null)
          break;
       
        line = line.trim();
        if(line.length() == 0)
          continue;
       
        if(line.charAt(0) != '#')
          antiIdles.add(line);
      } while(true);
     
      try { is.close(); } catch (Exception e) {}
    }
   
    //grab one
    int i = antiIdles.size();
    if(i == 0)
      return cs.antiIdle;
    i = (int)Math.floor(Math.random() * i);
    return antiIdles.get(i);
  }
 
  private void connectedLoop() throws Exception {
    lastAntiIdle = new Date().getTime();
    lastNullPacket = new Date().getTime();
   
    while(!s.isClosed() && connected) {
      long timeNow = new Date().getTime();
     
      //Send null packets every 30 seconds
      if(true) {
        long timeSinceNullPacket = timeNow - lastNullPacket;
        //Wait 30 seconds
        timeSinceNullPacket /= 1000;
        if(timeSinceNullPacket > 30) {
          lastNullPacket = timeNow;
          BNCSPacket p = new BNCSPacket(BNCSCommandIDs.SID_NULL);
          p.SendPacket(dos, cs.packetLog);
        }
      }
     
      //Send anti-idles every 5 minutes
      if((channelName != null) && cs.enableAntiIdle) {
        long timeSinceAntiIdle = timeNow - lastAntiIdle;
       
        //Wait 5 minutes
        timeSinceAntiIdle /= 1000;
        timeSinceAntiIdle /= 60;
        if(timeSinceAntiIdle >= cs.antiIdleTimer) {
          lastAntiIdle = timeNow;
          sendChat(getAntiIdle());
        }
      }
     
      if(dis.available() > 0) {
        BNCSPacketReader pr = new BNCSPacketReader(dis, cs.packetLog);
        BNetInputStream is = pr.getData();
       
        switch(pr.packetId) {
        case BNCSCommandIDs.SID_EXTRAWORK:
        case BNCSCommandIDs.SID_REQUIREDWORK:
          break;
         
        case BNCSCommandIDs.SID_NULL: {
          lastNullPacket = timeNow;
          BNCSPacket p = new BNCSPacket(BNCSCommandIDs.SID_NULL);
          p.SendPacket(dos, cs.packetLog);
          break;
        }
       
        case BNCSCommandIDs.SID_PING: {
          BNCSPacket p = new BNCSPacket(BNCSCommandIDs.SID_PING);
          p.writeDWord(is.readDWord());
          p.SendPacket(dos, cs.packetLog);
          break;
        }
       
        case BNCSCommandIDs.SID_AUTH_INFO:
        case BNCSCommandIDs.SID_STARTVERSIONING: {
          if(pr.packetId == BNCSCommandIDs.SID_AUTH_INFO) {
            nlsRevision = is.readDWord();
            serverToken = is.readDWord();
            is.skip(4)//int udpValue = is.readDWord();
          }
          long MPQFileTime = is.readQWord();
          String MPQFileName = is.readNTString();
          String ValueStr = is.readNTString();
         
          recieveInfo("MPQ: " + MPQFileName);
       
          byte extraData[] = null;
          if(is.available() == 0x80) {
            extraData = new byte[0x80];
            is.read(extraData, 0, 0x80);
          }
          assert(is.available() == 0);
         
          // Hash the CD key
          byte keyHash[] = null;
          byte keyHash2[] = null;
          if(nlsRevision != -1) {
            keyHash = HashMain.hashKey(clientToken, serverToken, cs.cdkey).getBuffer();
            if(cs.product == ConnectionSettings.PRODUCT_LORDOFDESTRUCTION)
              keyHash2 = HashMain.hashKey(clientToken, serverToken, cs.cdkeyLOD).getBuffer();
            if(cs.product == ConnectionSettings.PRODUCT_THEFROZENTHRONE)
              keyHash2 = HashMain.hashKey(clientToken, serverToken, cs.cdkeyTFT).getBuffer();
          }
         
          // Hash the game files
            String files[] = HashMain.getFiles(cs.product, HashMain.PLATFORM_INTEL);

          int exeHash;
                  int exeVersion;
                  String exeInfo;
                 
                  try {
                    String tmp = MPQFileName;
                    tmp = tmp.substring(tmp.indexOf("IX86")+4);
                    while((tmp.charAt(0) < 0x30) || (tmp.charAt(0) > 0x39))
                      tmp = tmp.substring(1);
              tmp = tmp.substring(0,tmp.indexOf("."));
                      int mpqNum = Integer.parseInt(tmp);
                     
                      exeHash = CheckRevision.checkRevision(ValueStr, files, mpqNum);
              exeVersion = HashMain.getExeVer(cs.product);
            exeInfo = HashMain.getExeInfo(cs.product);
                  } catch(Exception e) {
                    recieveError("Local hashing failed. Trying BNLS server.");
                   
                    BNLSProtocol.OutPacketBuffer exeHashBuf;
                    if((cs.bnlsServer == null)
                    || (cs.bnlsServer.length() == 0)) {
                      exeHashBuf = CheckRevisionBNLS.checkRevision(ValueStr, cs.product, MPQFileName, MPQFileTime);
                    } else {
                      exeHashBuf = CheckRevisionBNLS.checkRevision(ValueStr, cs.product, MPQFileName, MPQFileTime, cs.bnlsServer);
                    }
              BNetInputStream exeStream = new BNetInputStream(new java.io.ByteArrayInputStream(exeHashBuf.getBuffer()));
              exeStream.skipBytes(3);
              int success = exeStream.readDWord();
              if(success != 1) {
                Out.error(this.getClass().getName(), HexDump.hexDump(exeHashBuf.getBuffer()));
                throw new Exception("BNLS failed to complete 0x1A sucessfully");
              }
              exeVersion = exeStream.readDWord();
              exeHash = exeStream.readDWord();
              exeInfo = exeStream.readNTString();
              exeStream.readDWord(); // cookie
              /*int exeVerbyte =*/ exeStream.readDWord();
              assert(exeStream.available() == 0);
                  }
                 
                  if((exeVersion == 0) || (exeHash == 0) || (exeInfo == null) || (exeInfo.length() == 0)) {
                    recieveError("Checkrevision failed!");
                    setConnected(false);
                    break;
                  }

          // Respond
                  if(nlsRevision != -1) {
            BNCSPacket p = new BNCSPacket(BNCSCommandIDs.SID_AUTH_CHECK);
            p.writeDWord(clientToken);
            p.writeDWord(exeVersion);
            p.writeDWord(exeHash);
            if(keyHash2 == null)
              p.writeDWord(1);    // Number of keys
            else
              p.writeDWord(2);    // Number of keys
            p.writeDWord(0);      // Spawn?
           
            //For each key..
            if(keyHash.length != 36)
              throw new Exception("Invalid keyHash length");
            p.write(keyHash);
            if(keyHash2 != null) {
              if(keyHash2.length != 36)
                throw new Exception("Invalid keyHash2 length");
              p.write(keyHash2);
            }
           
            //Finally,
            p.writeNTString(exeInfo);
            p.writeNTString(cs.username);
            p.SendPacket(dos, cs.packetLog);
                  } else {
                    /* (DWORD)     Platform ID
                     * (DWORD)     Product ID
                     * (DWORD)     Version Byte
                     * (DWORD)     EXE Version
                     * (DWORD)     EXE Hash
                     * (STRING)    EXE Information
                     */
                    BNCSPacket p = new BNCSPacket(BNCSCommandIDs.SID_REPORTVERSION);
                    p.writeDWord(PlatformIDs.PLATFORM_IX86);
                    p.writeDWord(productID);
                    p.writeDWord(verByte);
            p.writeDWord(exeVersion);
            p.writeDWord(exeHash);
            p.writeNTString(exeInfo);
            p.SendPacket(dos, cs.packetLog);
                  }
          break;
        }
       
        case BNCSCommandIDs.SID_AUTH_CHECK: {
          int result = is.readDWord();
          String extraInfo = is.readNTString();
          assert(is.available() == 0);
         
          if(result != 0) {
            switch(result) {
            case 0x0101:
              recieveError("Invalid version");
              break;
            case 0x102:
              recieveError("Game version must be downgraded: " + extraInfo);
              break;
            case 0x200:
              recieveError("Invalid CD key");
              break;
            case 0x201:
              recieveError("CD key in use by " + extraInfo);
              break;
            case 0x202:
              recieveError("Banned key");
              break;
            case 0x203:
              recieveError("Wrong product for CD key");
              break;
            case 0x210:
              recieveError("Invalid second CD key");
              break;
            case 0x211:
              recieveError("Second CD key in use by " + extraInfo);
              break;
            case 0x212:
              recieveError("Banned second key");
              break;
            case 0x213:
              recieveError("Wrong product for second CD key");
              break;
            default:
              recieveError("Unknown SID_AUTH_CHECK result 0x" + Integer.toHexString(result));
              break;
            }
            setConnected(false);
            break;
          }
         
          recieveInfo("Passed CD key challenge and CheckRevision");
          sendPassword();
          break;
        }
       
        case BNCSCommandIDs.SID_AUTH_ACCOUNTLOGON: {
          /* (DWORD)     Status
           * (BYTE[32])   Salt (s)
           * (BYTE[32])   Server Key (B)
           *
           * 0x00: Logon accepted, requires proof.
           * 0x01: Account doesn't exist.
           * 0x05: Account requires upgrade.
           * Other: Unknown (failure).
           */
          int status = is.readDWord();
          switch(status) {
          case 0x00:
            recieveInfo("Login accepted; requires proof.");
            break;
          case 0x01:
            recieveError("Account doesn't exist; creating...");
           
            if(srp == null) {
              recieveError("SRP is not initialized!");
              setConnected(false);
              break;
            }

                byte[] salt = new byte[32];
                new Random().nextBytes(salt);
                byte[] verifier = srp.get_v(salt).toByteArray();

                if(salt.length != 32)
                  throw new Exception("Salt length wasn't 32!");
                if(verifier.length != 32)
                  throw new Exception("Verifier length wasn't 32!");
               
                BNCSPacket p = new BNCSPacket(BNCSCommandIDs.SID_AUTH_ACCOUNTCREATE);
                p.write(salt);
                p.write(verifier);
                p.writeNTString(cs.username);
                p.SendPacket(dos, cs.packetLog);
           
            break;
          case 0x05:
            recieveError("Account requires upgrade");
            setConnected(false);
            break;
          default:
            recieveError("Unknown SID_AUTH_ACCOUNTLOGON status 0x" + Integer.toHexString(status));
            setConnected(false);
            break;
          }
         
          if(status != 0)
            break;
         
          if(srp == null) {
            recieveError("SRP is not initialized!");
            setConnected(false);
            break;
          }
         
          byte s[] = new byte[32];
          byte B[] = new byte[32];
          is.read(s, 0, 32);
          is.read(B, 0, 32);

          byte M1[] = srp.getM1(s, B);
          proof_M2 = srp.getM2(s, B);
          if(M1.length != 20)
            throw new Exception("Invalid M1 length");

          BNCSPacket p = new BNCSPacket(BNCSCommandIDs.SID_AUTH_ACCOUNTLOGONPROOF);
          p.write(M1);
          p.SendPacket(dos, cs.packetLog);
          break;
        }
       
        case BNCSCommandIDs.SID_AUTH_ACCOUNTCREATE: {
          /*
           * (DWORD)     Status
           * 0x00: Successfully created account name.
           * 0x04: Name already exists.
           * 0x07: Name is too short/blank.
           * 0x08: Name contains an illegal character.
           * 0x09: Name contains an illegal word.
           * 0x0a: Name contains too few alphanumeric characters.
           * 0x0b: Name contains adjacent punctuation characters.
           * 0x0c: Name contains too many punctuation characters.
           * Any other: Name already exists.
           */
          int status = is.readDWord();
          switch(status) {
          case 0x00:
            recieveInfo("Create account succeeded; logging in.");
            sendPassword();
            break;
          default:
            recieveError("Create account failed with error code 0x" + Integer.toHexString(status));
            break;
          }
          break;
        }
       
        case BNCSCommandIDs.SID_AUTH_ACCOUNTLOGONPROOF: {
          /* (DWORD)     Status
           * (BYTE[20])   Server Password Proof (M2)
           * (STRING)    Additional information
           *
           * Status:
           * 0x00: Logon successful.
           * 0x02: Incorrect password.
           * 0x0E: An email address should be registered for this account.
           * 0x0F: Custom error. A string at the end of this message contains the error.
           */
          int status = is.readDWord();
          byte server_M2[] = new byte[20];
          is.read(server_M2, 0, 20);
          String additionalInfo = null;
          if(is.available() != 0)
            additionalInfo = is.readNTString();
         
          switch(status) {
          case 0x00:
            break;
          case 0x02:
            recieveError("Incorrect password");
            setConnected(false);
            break;
          case 0x0E:
            recieveError("An email address should be registered for this account.");
            registerEmail();
            break;
          case 0x0F:
            recieveError("Custom bnet error: " + additionalInfo);
            setConnected(false);
            break;
          default:
            recieveError("Unknown SID_AUTH_ACCOUNTLOGONPROOF status: 0x" + Integer.toHexString(status));
            setConnected(false);
            break;
          }
          if(!isConnected())
            break;

          for(int i = 0; i < 20; i++) {
            if(server_M2[i] != proof_M2[i])
              throw new Exception("Server couldn't prove password");
          }

          recieveInfo("Login successful; entering chat.");

          BNCSPacket p = new BNCSPacket(BNCSCommandIDs.SID_ENTERCHAT);
          p.writeNTString("");
          p.writeNTString("");
          p.SendPacket(dos, cs.packetLog);
          break;
        }
       
        case BNCSCommandIDs.SID_LOGONRESPONSE2: {
          int result = is.readDWord();
          switch(result) {
          case 0x00// Success
            recieveInfo("Login successful; entering chat.");

            BNCSPacket p = new BNCSPacket(BNCSCommandIDs.SID_ENTERCHAT);
            p.writeNTString("");
            p.writeNTString("");
            p.SendPacket(dos, cs.packetLog);
            break;
          case 0x01// Account doesn't exist
            recieveInfo("Account doesn't exist; creating...");
           
            int[] passwordHash = BrokenSHA1.calcHashBuffer(cs.password.toLowerCase().getBytes());
           
            p = new BNCSPacket(BNCSCommandIDs.SID_CREATEACCOUNT2);
            p.writeDWord(passwordHash[0]);
            p.writeDWord(passwordHash[1]);
            p.writeDWord(passwordHash[2]);
            p.writeDWord(passwordHash[3]);
            p.writeDWord(passwordHash[4]);
            p.writeNTString(cs.username);
            p.SendPacket(dos, cs.packetLog);
            break;
          case 0x02// Invalid password;
            recieveError("Incorrect password");
            setConnected(false);
            break;
          case 0x06// Account is cloed
            recieveError("Your account is closed.");
            setConnected(false);
            break;
          default:
            recieveError("Unknown SID_LOGONRESPONSE2 result 0x" + Integer.toHexString(result));
            setConnected(false);
            break;
          }
          break;
        }
       
        case BNCSCommandIDs.SID_CLIENTID: {
          //Sends new registration values; no longer used
          break;
        }
       
        case BNCSCommandIDs.SID_LOGONCHALLENGE: {
          serverToken = is.readDWord();
          break;
        }
       
        case BNCSCommandIDs.SID_LOGONCHALLENGEEX: {
          /*int udpToken =*/ is.readDWord();
          serverToken = is.readDWord();
          break;
        }
       
        case BNCSCommandIDs.SID_CREATEACCOUNT2: {
          int status = is.readDWord();
          /*String suggestion =*/ is.readNTString();
         
          switch(status) {
          case 0x00:
            recieveInfo("Account created");
            sendPassword();
            break;
          case 0x02:
            recieveError("Name contained invalid characters");
            setConnected(false);
            break;
          case 0x03:
            recieveError("Name contained a banned word");
            setConnected(false);
            break;
          case 0x04:
            recieveError("Account already exists");
            setConnected(false);
            break;
          case 0x06:
            recieveError("Name did not contain enough alphanumeric characters");
            setConnected(false);
            break;
          default:
            recieveError("Unknown SID_CREATEACCOUNT2 status 0x" + Integer.toHexString(status));
            setConnected(false);
            break;
          }
          break;
        }
       
        case BNCSCommandIDs.SID_SETEMAIL: {
          recieveError("An email address should be registered for this account.");
          registerEmail();
          break;
        }
       
        case BNCSCommandIDs.SID_ENTERCHAT: {
          String uniqueUserName = is.readNTString();
          myStatString = new StatString(is.readNTString());
          /*String accountName =*/ is.readNTString();
         
          myUser = BNetUser.getBNetUser(uniqueUserName, cs.myRealm);
          recieveInfo("Logged in as " + myUser.getFullLogonName());
          titleChanged();
         
          // We are officially logged in!
         
          // Get MOTD
          BNCSPacket p = new BNCSPacket(BNCSCommandIDs.SID_NEWS_INFO);
          p.writeDWord((int)(new java.util.Date().getTime() / 1000)); // timestamp
          p.SendPacket(dos, cs.packetLog);
         
          // Get friends list
          p = new BNCSPacket(BNCSCommandIDs.SID_FRIENDSLIST);
          p.SendPacket(dos, cs.packetLog);
         
          // Join home channel
          joinChannel(cs.channel);
          break;
        }
       
        case BNCSCommandIDs.SID_NEWS_INFO: {
          int numEntries = is.readByte();
          //int lastLogon = is.readDWord();
          //int oldestNews = is.readDWord();
          //int newestNews = is.readDWord();;
          is.skip(12);
         
          for(int i = 0; i < numEntries; i++) {
            int timeStamp = is.readDWord();
            String news = is.readNTString().trim();
            if(timeStamp == 0// MOTD
              recieveInfo(news);
          }
         
          break;
        }
       
        case BNCSCommandIDs.SID_CHATEVENT: {
          int eid = is.readDWord();
          int flags = is.readDWord();
          int ping = is.readDWord();
          is.skip(12);
        //  is.readDWord();  // IP Address (defunct)
        //  is.readDWord();  // Account number (defunct)
        //  is.readDWord(); // Registration authority (defunct)
          String username = is.readNTString();
          String text = is.readNTString();

          BNetUser user = null;
          switch(eid) {
          case BNCSChatEventIDs.EID_SHOWUSER:
          case BNCSChatEventIDs.EID_USERFLAGS:
          case BNCSChatEventIDs.EID_JOIN:
          case BNCSChatEventIDs.EID_LEAVE:
          case BNCSChatEventIDs.EID_TALK:
          case BNCSChatEventIDs.EID_EMOTE:
          case BNCSChatEventIDs.EID_WHISPERSENT:
          case BNCSChatEventIDs.EID_WHISPER:
            switch(productID) {
            case ProductIDs.PRODUCT_D2DV:
            case ProductIDs.PRODUCT_D2XP:
              int asterisk = username.indexOf('*');
              if(asterisk >= 0)
                username = username.substring(asterisk+1);
              break;
            }
           
            user = BNetUser.getBNetUser(username, cs.myRealm);
            user.setFlags(flags);
            user.setPing(ping);
            break;
          }
         
          switch(eid) {
          case BNCSChatEventIDs.EID_SHOWUSER:
          case BNCSChatEventIDs.EID_USERFLAGS:
            channelUser(user, new StatString(text));
            break;
          case BNCSChatEventIDs.EID_JOIN:
            channelJoin(user, new StatString(text));
            break;
          case BNCSChatEventIDs.EID_LEAVE:
            channelLeave(user);
            break;
          case BNCSChatEventIDs.EID_TALK:
            recieveChat(user, text);
            break;
          case BNCSChatEventIDs.EID_EMOTE:
            recieveEmote(user, text);
            break;
          case BNCSChatEventIDs.EID_INFO:
            recieveInfo(text);
            break;
          case BNCSChatEventIDs.EID_ERROR:
            recieveError(text);
            break;
          case BNCSChatEventIDs.EID_CHANNEL:
            channelName = text;
            joinedChannel(text);
            titleChanged();
            break;
          case BNCSChatEventIDs.EID_WHISPERSENT:
            whisperSent(user, text);
            break;
          case BNCSChatEventIDs.EID_WHISPER:
            whisperRecieved(user, text);
            break;
          case BNCSChatEventIDs.EID_CHANNELDOESNOTEXIST:
            BNCSPacket p = new BNCSPacket(BNCSCommandIDs.SID_JOINCHANNEL);
            p.writeDWord(2); // create join
            p.writeNTString(text);
            p.SendPacket(dos, cs.packetLog);
            break;
          case BNCSChatEventIDs.EID_CHANNELRESTRICTED:
            recieveError("Channel " + text + " is restricted");
            break;
          case BNCSChatEventIDs.EID_CHANNELFULL:
            recieveError("Channel " + text + " is full");
            break;
          default:
            recieveError("Unknown SID_CHATEVENT EID 0x" + Integer.toHexString(eid) + ": " + text);
            break;
          }
         
          break;
        }
       
        case BNCSCommandIDs.SID_MESSAGEBOX: {
          /*int style =*/ is.readDWord();
          String text = is.readNTString();
          String caption = is.readNTString();
         
          recieveInfo("<" + caption + "> " + text);
          break;
        }
       
        case BNCSCommandIDs.SID_FLOODDETECTED: {
          recieveError("You have been disconnected for flooding.");
          setConnected(false);
          break;
        }
       
        /*  .----------.
         *  |  Realms  |
         *  '----------'
         */
        case BNCSCommandIDs.SID_QUERYREALMS2: {
          /* (DWORD)     Unknown0
           * (DWORD)     Number of Realms
           *
           * For each realm:
           * (DWORD)     UnknownR0
           * (STRING)    Realm Name
           * (STRING)    Realm Description
           */
          is.readDWord();
          int numRealms = is.readDWord();
          String realms[] = new String[numRealms];
          for(int i = 0; i < numRealms; i++) {
            is.readDWord();
            realms[i] = is.readNTString();
            is.readNTString();
          }
          queryRealms2(realms);
          break;
        }
       
        case  BNCSCommandIDs.SID_LOGONREALMEX: {
          /* (DWORD)     Cookie
           * (DWORD)     Status
           * (DWORD[2])   MCP Chunk 1
           * (DWORD)     IP
           * (DWORD)     Port
           * (DWORD[12])   MCP Chunk 2
           * (STRING)    BNCS unique name
           * (WORD)     Unknown
           */
          if(pr.packetLength < 12)
            throw new Exception("pr.packetLength < 12");
          else if(pr.packetLength == 12) {
            /*int cookie =*/ is.readDWord();
            int status = is.readDWord();
            switch(status) {
            case 0x80000001:
              recieveError("Realm is unavailable.");
              break;
            case 0x80000002:
              recieveError("Realm logon failed");
              break;
            default:
              throw new Exception("Unknown status code 0x" + Integer.toHexString(status));
            }
          } else {
            int MCPChunk1[] = new int[4];
            MCPChunk1[0] = is.readDWord();
            MCPChunk1[1] = is.readDWord();
            MCPChunk1[2] = is.readDWord();
            MCPChunk1[3] = is.readDWord();
            int ip = is.readDWord();
            int port = is.readDWord();
            port = ((port & 0xFF00) >> 8) | ((port & 0x00FF) << 8);
            int MCPChunk2[] = new int[12];
            MCPChunk2[0] = is.readDWord();
            MCPChunk2[1] = is.readDWord();
            MCPChunk2[2] = is.readDWord();
            MCPChunk2[3] = is.readDWord();
            MCPChunk2[4] = is.readDWord();
            MCPChunk2[5] = is.readDWord();
            MCPChunk2[6] = is.readDWord();
            MCPChunk2[7] = is.readDWord();
            MCPChunk2[8] = is.readDWord();
            MCPChunk2[9] = is.readDWord();
            MCPChunk2[10] = is.readDWord();
            MCPChunk2[11] = is.readDWord();
            String uniqueName = is.readNTString();
            /*int unknown =*/ is.readWord();
            logonRealmEx(MCPChunk1, ip, port, MCPChunk2, uniqueName);
          }
         
          break;
        }
       
        /*  .-----------.
         *  |  Profile  |
         *  '-----------'
         */
       
        case BNCSCommandIDs.SID_READUSERDATA: {
          /* (DWORD)     Number of accounts
           * (DWORD)     Number of keys
           * (DWORD)     Request ID
           * (STRING[])   Requested Key Values
           */
          int numAccounts = is.readDWord();
          int numKeys = is.readDWord();
          @SuppressWarnings("unchecked")
          ArrayList<String> keys = (ArrayList<String>)CookieUtility.destroyCookie(is.readDWord());
         
          if(numAccounts != 1)
            throw new IllegalStateException("SID_READUSERDATA with numAccounts != 1");
         
          recieveInfo("Profile for " + keys.remove(0));
          for(int i = 0; i < numKeys; i++) {
            String key = keys.get(i);
            String value = is.readNTString();
            if((key == null) || (key.length() == 0) || (value.length() == 0))
              continue;
            value = prettyProfileValue(key, value);
            recieveInfo(key + " = " + value);
          }
          break;
        }
       
        /*  .-----------.
         *  |  Friends  |
         *  '-----------'
         */
       
        case BNCSCommandIDs.SID_FRIENDSLIST: {
          /* (BYTE)     Number of Entries
           *
           * For each member:
           * (STRING)    Account
           * (BYTE)     Status
           * (BYTE)     Location
           * (DWORD)     ProductID
           * (STRING)    Location name
           */
          byte numEntries = is.readByte();
          FriendEntry[] entries = new FriendEntry[numEntries];
         
          for(int i = 0; i < numEntries; i++) {
            String uAccount = is.readNTString();
            byte uStatus = is.readByte();
            byte uLocation = is.readByte();
            int uProduct = is.readDWord();
            String uLocationName = is.readNTString();
           
            entries[i] = new FriendEntry(uAccount, uStatus, uLocation, uProduct, uLocationName);
          }
         
          friendsList(entries);
          break;
        }
       
        case BNCSCommandIDs.SID_FRIENDSUPDATE: {
          /* (BYTE)     Entry number
           * (BYTE)     Friend Location
           * (BYTE)     Friend Status
           * (DWORD)     ProductID
           * (STRING)    Location
           */
          byte fEntry = is.readByte();
          byte fLocation = is.readByte();
          byte fStatus = is.readByte();
          int fProduct = is.readDWord();
          String fLocationName = is.readNTString();
         
          friendsUpdate(new FriendEntry(fEntry, fStatus, fLocation, fProduct, fLocationName));
          break;
        }
       
        case BNCSCommandIDs.SID_FRIENDSADD: {
          /* (STRING)    Account
           * (BYTE)     Friend Type
           * (BYTE)     Friend Status
           * (DWORD)     ProductID
           * (STRING)    Location
           */
          String fAccount = is.readNTString();
          byte fLocation = is.readByte();
          byte fStatus = is.readByte();
          int fProduct = is.readDWord();
          String fLocationName = is.readNTString();

          friendsAdd(new FriendEntry(fAccount, fStatus, fLocation, fProduct, fLocationName));
          break;
        }
       
        case BNCSCommandIDs.SID_FRIENDSREMOVE: {
          /* (BYTE)     Entry Number
           */
          byte entry = is.readByte();
         
          friendsRemove(entry);
          break;
        }
       
        case BNCSCommandIDs.SID_FRIENDSPOSITION: {
          /* (BYTE)     Old Position
           * (BYTE)     New Position
           */
          byte oldPosition = is.readByte();
          byte newPosition = is.readByte();
         
          friendsPosition(oldPosition, newPosition);
          break;
        }
       
        /*  .--------.
         *  |  Clan  |
         *  '--------'
         */

        // SID_CLANFINDCANDIDATES
        // SID_CLANINVITEMULTIPLE
        // SID_CLANCREATIONINVITATION
        // SID_CLANDISBAND
        // SID_CLANMAKECHIEFTAIN
       
        case BNCSCommandIDs.SID_CLANINFO: {
          /* (BYTE)     Unknown (0)
           * (DWORD)     Clan tag
           * (BYTE)     Rank
           */
          is.readByte();
          myClan = is.readDWord();
          myClanRank = is.readByte();
          titleChanged();
         
          //TODO: clanInfo(myClan, myClanRank);
         
          // Get clan list
          BNCSPacket p = new BNCSPacket(BNCSCommandIDs.SID_CLANMEMBERLIST);
          p.writeDWord(0)// Cookie
          p.SendPacket(dos, cs.packetLog);
          break;
        }
       
        // SID_CLANQUITNOTIFY
        // SID_CLANINVITATION
        // SID_CLANREMOVEMEMBER
        // SID_CLANINVITATIONRESPONSE
       
        case BNCSCommandIDs.SID_CLANRANKCHANGE: {
          int cookie = is.readDWord();
          byte status = is.readByte();
         
          Object obj = CookieUtility.destroyCookie(cookie);
          String statusCode = null;
          switch(status) {
          case ClanStatusIDs.CLANSTATUS_SUCCESS:
            statusCode = "Successfully changed rank";
            break;
          case 0x01:
            statusCode = "Failed to change rank";
            break;
          case ClanStatusIDs.CLANSTATUS_TOO_SOON:
            statusCode = "Cannot change user's rank yet";
            break;
          case ClanStatusIDs.CLANSTATUS_NOT_AUTHORIZED:
            statusCode = "Not authorized to change user rank*";
            break;
          case 0x08:
            statusCode = "Not allowed to change user rank**";
            break;
          default:  statusCode = "Unknown ClanStatusID 0x" + Integer.toHexString(status);
          }
         
          recieveInfo(statusCode + "\n" + obj.toString());
          // TODO: clanRankChange(obj, status)
         
          break;
        }
       
        case BNCSCommandIDs.SID_CLANMOTD: {
          /* (DWORD)     Cookie
           * (DWORD)     Unknown (0)
           * (STRING)    MOTD
           */
          int cookieId = is.readDWord();
          is.readDWord();
          String text = is.readNTString();
         
          Object cookie = CookieUtility.destroyCookie(cookieId);
          clanMOTD(cookie, text);
          break;
        }
       
        case BNCSCommandIDs.SID_CLANMEMBERLIST: {
          /* (DWORD)     Cookie
           * (BYTE)     Number of Members
           *
           * For each member:
           * (STRING)    Username
           * (BYTE)     Rank
           * (BYTE)     Online Status
           * (STRING)    Location
           */
          is.readDWord();
          byte numMembers = is.readByte();
          ClanMember[] members = new ClanMember[numMembers];
         
          for(int i = 0; i < numMembers; i++) {
            String uName = is.readNTString();
            byte uRank = is.readByte();
            byte uOnline = is.readByte();
            String uLocation = is.readNTString();
           
            members[i] = new ClanMember(uName, uRank, uOnline, uLocation);
          }
         
          clanMemberList(members);
          break;
        }
       
        case BNCSCommandIDs.SID_CLANMEMBERREMOVED: {
          /* (STRING)    Username
           */
          String username = is.readNTString();
          clanMemberRemoved(username);
          break;
        }

        case BNCSCommandIDs.SID_CLANMEMBERSTATUSCHANGE: {
          /* (STRING)    Username
           * (BYTE)     Rank
           * (BYTE)     Status
           * (STRING)    Location
           */
          String username = is.readNTString();
          byte rank = is.readByte();
          byte status = is.readByte();
          String location = is.readNTString();
         
          clanMemberStatusChange(new ClanMember(username, rank, status, location));
          break;
        }
       
        case BNCSCommandIDs.SID_CLANMEMBERRANKCHANGE: {
          /* (BYTE)     Old rank
           * (BYTE)     New rank
           * (STRING)    Clan member who changed your rank
           */
          byte oldRank = is.readByte();
          byte newRank = is.readByte();
          String user = is.readNTString();
          recieveInfo("Rank changed from " + ClanRankIDs.ClanRank[oldRank] + " to " + ClanRankIDs.ClanRank[newRank] + " by " + user);
          clanMemberRankChange(oldRank, newRank, user);
          break;
        }
       
        // TODO: SID_CLANMEMBERINFORMATION
       
        default:
          recieveError("Unknown SID 0x" + Integer.toHexString(pr.packetId) + "\n" + HexDump.hexDump(pr.data));
          break;
        }
      } else {
        sleep(10);
        yield();
      }
    }
  }
 
  public boolean isOp() {
    Integer myFlags = myUser.getFlags();
    if(myFlags == null)
      return false;
    return (myFlags & 0x02) == 0x02;
  }
 
  private void registerEmail() throws Exception {
    if(cs.email == null)
      return;
    if(cs.email.length() == 0)
      return;
    recieveInfo("Register email address: " + cs.email);
    BNCSPacket p = new BNCSPacket(BNCSCommandIDs.SID_SETEMAIL);
    p.writeNTString(cs.email);
    p.SendPacket(dos, cs.packetLog);
  }

  public void joinChannel(String channel) throws Exception {
    BNCSPacket p = new BNCSPacket(BNCSCommandIDs.SID_JOINCHANNEL);
    p.writeDWord(0); // nocreate join
    p.writeNTString(channel);
    p.SendPacket(dos, cs.packetLog);
  }
 
  public void sendChat(String text) {
    text = cleanText(text);
   
    try {
      if(text.substring(0, 3).equals("/j ")) {
        Out.info(this.getClass().getName(), "Sending join packet");
        joinChannel(text.substring(3));
        return;
      }
      if(text.substring(0, 6).equals("/join ")) {
        Out.info(this.getClass().getName(), "Sending join packet");
        joinChannel(text.substring(6));
        return;
      }
    } catch(Exception e) {}
   
    super.sendChat(text);
  }
 
  public void sendChatNow(String text) {
    super.sendChatNow(text);
   

    switch(productID) {
    case ProductIDs.PRODUCT_D2DV:
    case ProductIDs.PRODUCT_D2XP:
      if((text.length() > 1) && (text.charAt(0) == '/')) {
        String cmd = text.substring(1);
        int i = cmd.indexOf(' ');
        if(i != -1) {
          String theRest = cmd.substring(i+1);
          cmd = cmd.substring(0, i);
       
          if(cmd.equals("w")
          || cmd.equals("m")
          || cmd.equals("whois")
          || cmd.equals("ignre")
          || cmd.equals("squelch")) {
            if(theRest.charAt(0) != '*')
              text = '/' + cmd + " *" + theRest;
          }
         
        }
      }
      break;
    }
   
    //Write the packet
    try {
      BNCSPacket p = new BNCSPacket(BNCSCommandIDs.SID_CHATCOMMAND);
      p.writeNTString(text);
      p.SendPacket(dos, cs.packetLog);
    } catch(SocketException e) {
      if(cs.autoconnect)
        reconnect();
      else
        setConnected(false);
      return;
    } catch (IOException e) {
      e.printStackTrace();
      System.exit(1);
    }

    if(text.charAt(0) != '/')
      recieveChat(myUser, text);
  }

  public void sendClanRankChange(String user, int newRank) throws Exception {
    switch(productID) {
    case ProductIDs.PRODUCT_WAR3:
    case ProductIDs.PRODUCT_W3XP:
      break;
    default:
      throw new UnsupportedFeatureException("Only WAR3/W3XP support clans.");
    }
   
    LinkedList<Object> obj = new LinkedList<Object>();
    obj.add("This is the cookie for setRank:");
    obj.add(user);
    obj.add(newRank);
   
    int id = CookieUtility.createCookie(obj);
   
    BNCSPacket p = new BNCSPacket(BNCSCommandIDs.SID_CLANRANKCHANGE);
    p.writeDWord(id);    //Cookie
    p.writeNTString(user)//Username
    p.writeByte(newRank)//New rank
    p.SendPacket(dos, cs.packetLog);
  }

  public void sendClanMOTD(Object cookie) throws Exception {
    switch(productID) {
    case ProductIDs.PRODUCT_WAR3:
    case ProductIDs.PRODUCT_W3XP:
      break;
    default:
      throw new UnsupportedFeatureException("Only WAR3/W3XP support MOTD.");
    }
   
    BNCSPacket p = new BNCSPacket(BNCSCommandIDs.SID_CLANMOTD);
    p.writeDWord(CookieUtility.createCookie(cookie));
    p.SendPacket(dos, cs.packetLog);
  }
 
  public void sendClanSetMOTD(String text) throws Exception {
    switch(productID) {
    case ProductIDs.PRODUCT_WAR3:
    case ProductIDs.PRODUCT_W3XP:
      break;
    default:
      throw new UnsupportedFeatureException("Only WAR3/W3XP support MOTD.");
    }
   
    BNCSPacket p = new BNCSPacket(BNCSCommandIDs.SID_CLANSETMOTD);
    p.writeDWord(0)//Cookie
    p.writeNTString(text);
    p.SendPacket(dos, cs.packetLog);
  }
 
  public void sendQueryRealms() throws Exception {
    /* (DWORD)     Unused (0)
     * (DWORD)     Unused (0)
     * (STRING)    Unknown (empty)
     */
    BNCSPacket p = new BNCSPacket(BNCSCommandIDs.SID_QUERYREALMS2);
    p.SendPacket(dos, cs.packetLog);
  }
 
  public void sendLogonRealmEx(String realmTitle) throws Exception {
    switch(productID) {
    case ProductIDs.PRODUCT_D2DV:
    case ProductIDs.PRODUCT_D2XP:
      break;
    default:
      throw new UnsupportedFeatureException("Only D2DV/D2XP support realms");
    }
   
    /* (DWORD)     Client key
     * (DWORD[5])   Hashed realm password
     * (STRING)    Realm title
     */
    int[] hash = DoubleHash.doubleHash("password", clientToken, serverToken);
   
    BNCSPacket p = new BNCSPacket(BNCSCommandIDs.SID_LOGONREALMEX);
    p.writeDWord(clientToken);
    p.writeDWord(hash[0]);
    p.writeDWord(hash[1]);
    p.writeDWord(hash[2]);
    p.writeDWord(hash[3]);
    p.writeDWord(hash[4]);
    p.writeNTString(realmTitle);
    p.SendPacket(dos, cs.packetLog);
  }
 
  private String prettyProfileValue(String key, String value) {
    if("System\\Account Created".equals(key)
    || "System\\Last Logon".equals(key)
    || "System\\Last Logoff".equals(key)) {
      String parts[] = value.split(" ", 2);
      long time = Long.parseLong(parts[0]);
      time <<= 32;
      time += Long.parseLong(parts[1]);
     
      return TimeFormatter.fileTime(time).toString();
    } else
    if("System\\Time Logged".equals(key)) {
      long time = Long.parseLong(value);
      time *= 1000;
      return TimeFormatter.formatTime(time);
    }
   
    return value;
  }
  public void sendProfile(String user) throws Exception {
    /*BNCSPacket p = new BNCSPacket(BNCSCommandIDs.SID_PROFILE);
    p.writeDWord(CookieUtility.createCookie(user));
    p.writeNTString(user);
    p.SendPacket(dos, cs.packetLog);*/
    /* (DWORD)     Number of Accounts
     * (DWORD)     Number of Keys
     * (DWORD)     Request ID
     * (STRING[])   Requested Accounts
     * (STRING[])   Requested Keys
     */
    ArrayList<String> keys = new ArrayList<String>();
    keys.add(user);
    keys.add("profile\\sex");
    keys.add("profile\\age");
    keys.add("profile\\location");
    keys.add("profile\\description");
    keys.add("profile\\dbkey1");
    keys.add("profile\\dbkey2");
    if(myUser.equals(user)) {
      keys.add("System\\Account Created");
      keys.add("System\\Last Logon");
      keys.add("System\\Last Logoff");
      keys.add("System\\Time Logged");
      keys.add("System\\Username");
    }
   
    BNCSPacket p = new BNCSPacket(BNCSCommandIDs.SID_READUSERDATA);
    p.writeDWord(1);
    p.writeDWord(keys.size() - 1);
    p.writeDWord(CookieUtility.createCookie(keys));
    p.writeNTString(user);
    for(int i = 1; i < keys.size(); i++)
      p.writeNTString(keys.get(i));
    p.SendPacket(dos, cs.packetLog);
    
  }
 
  public String toString() {
    String out = "BNU-Bot " + CurrentVersion.version();
   
    if(channelName != null)
      out += " - [ #" + channelName + " ]";
   
    if(myUser != null) {
      out += " - [ ";
     
      if(myClanRank > 0) {
        out += "Clan ";
        out += HexDump.DWordToPretty(myClan);
        out += " ";
        out += clanRanks[myClanRank-1];
        out += " ";
      }
      out += myUser.getShortLogonName() + " ]";
    }
   
    return out;
  }
 
  public void reconnect() {
    forceReconnect = true;
    setConnected(false);
  }

  public int getProductID() {
    return productID;
  }
}
TOP

Related Classes of net.bnubot.core.bncs.BNCSConnection

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.