/*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later
* version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*/
package net.sf.l2j.gameserver;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.RSAKeyGenParameterSpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import javolution.util.FastList;
import javolution.util.FastMap;
import net.sf.l2j.Config;
import net.sf.l2j.gameserver.gameserverpackets.AuthRequest;
import net.sf.l2j.gameserver.gameserverpackets.BlowFishKey;
import net.sf.l2j.gameserver.gameserverpackets.ChangeAccessLevel;
import net.sf.l2j.gameserver.gameserverpackets.GameServerBasePacket;
import net.sf.l2j.gameserver.gameserverpackets.PlayerAuthRequest;
import net.sf.l2j.gameserver.gameserverpackets.PlayerInGame;
import net.sf.l2j.gameserver.gameserverpackets.PlayerLogout;
import net.sf.l2j.gameserver.gameserverpackets.ServerStatus;
import net.sf.l2j.gameserver.loginserverpackets.AuthResponse;
import net.sf.l2j.gameserver.loginserverpackets.InitLS;
import net.sf.l2j.gameserver.loginserverpackets.KickPlayer;
import net.sf.l2j.gameserver.loginserverpackets.LoginServerFail;
import net.sf.l2j.gameserver.loginserverpackets.PlayerAuthResponse;
import net.sf.l2j.gameserver.model.L2World;
import net.sf.l2j.gameserver.model.actor.instance.L2PcInstance;
import net.sf.l2j.gameserver.network.L2GameClient;
import net.sf.l2j.gameserver.network.L2GameClient.GameClientState;
import net.sf.l2j.gameserver.serverpackets.CharSelectionInfo;
import net.sf.l2j.gameserver.serverpackets.LoginFail;
import net.sf.l2j.loginserver.crypt.NewCrypt;
import net.sf.l2j.util.Rnd;
import net.sf.l2j.util.Util;
public class LoginServerThread extends Thread
{
protected static final Logger _log = Logger.getLogger(LoginServerThread.class.getName());
/** The LoginServerThread singleton */
private static LoginServerThread _instance;
/** {@see net.sf.l2j.loginserver.LoginServer#PROTOCOL_REV } */
private static final int REVISION = 0x0102;
private RSAPublicKey _publicKey;
private String _hostname;
private int _port;
private int _gamePort;
private Socket _loginSocket;
private InputStream _in;
private OutputStream _out;
/**
* The BlowFish engine used to encrypt packets
* It is first initialized with a unified key:
* "_;v.]05-31!|+-%xT!^[$\00"
*
* and then after handshake, with a new key sent by
* loginserver during the handshake. This new key is stored
* in {@link #_blowfishKey}
*/
private NewCrypt _blowfish;
private byte[] _blowfishKey;
private byte[] _hexID;
private boolean _acceptAlternate;
private int _requestID;
private int _serverID;
private boolean _reserveHost;
private int _maxPlayer;
private List _waitingClients;
private Map _accountsInGameServer;
private int _status;
private String _serverName;
private String _gameExternalHost;
private String _gameInternalHost;
public LoginServerThread()
{
super("LoginServerThread");
_port = Config.GAME_SERVER_LOGIN_PORT;
_gamePort = Config.PORT_GAME;
_hostname = Config.GAME_SERVER_LOGIN_HOST;
_hexID = Config.HEX_ID;
if(_hexID == null)
{
_requestID = Config.REQUEST_ID;
_hexID = generateHex(16);
}
else
{
_requestID = Config.SERVER_ID;
}
_acceptAlternate = Config.ACCEPT_ALTERNATE_ID;
_reserveHost = Config.RESERVE_HOST_ON_LOGIN;
_gameExternalHost = Config.EXTERNAL_HOSTNAME;
_gameInternalHost = Config.INTERNAL_HOSTNAME;
_waitingClients = new FastList();
_accountsInGameServer = new FastMap().setShared(true);
_maxPlayer = Config.MAXIMUM_ONLINE_USERS;
}
public static LoginServerThread getInstance()
{
if(_instance == null)
{
_instance = new LoginServerThread();
}
return _instance;
}
@Override
public void run()
{
while(true)
{
int lengthHi =0;
int lengthLo =0;
int length = 0;
boolean checksumOk = false;
try
{
// Connection
_log.info("Connecting to login on "+_hostname+":"+_port);
_loginSocket = new Socket(_hostname,_port);
_in = _loginSocket.getInputStream();
_out = new BufferedOutputStream(_loginSocket.getOutputStream());
//init Blowfish
_blowfishKey = generateHex(40);
_blowfish = new NewCrypt("_;v.]05-31!|+-%xT!^[$\00");
while (true)
{
lengthLo = _in.read();
lengthHi = _in.read();
length= lengthHi*256 + lengthLo;
if (lengthHi < 0 )
{
_log.finer("LoginServerThread: Login terminated the connection.");
break;
}
byte[] incoming = new byte[length];
incoming[0] = (byte) lengthLo;
incoming[1] = (byte) lengthHi;
int receivedBytes = 0;
int newBytes = 0;
while (newBytes != -1 && receivedBytes 0)
{
FastList playerList = new FastList();
for(L2PcInstance player : L2World.getInstance().getAllPlayers())
{
playerList.add(player.getAccountName());
}
PlayerInGame pig = new PlayerInGame(playerList);
sendPacket(pig);
}
break;
case 03:
PlayerAuthResponse par = new PlayerAuthResponse(decrypt);
String account = par.getAccount();
WaitingClient wcToRemove = null;
synchronized(_waitingClients)
{
for(WaitingClient wc : _waitingClients)
{
if(wc.account.equals(account))
{
wcToRemove = wc;
}
}
}
if(wcToRemove != null)
{
if (par.isAuthed())
{
if (Config.DEBUG)_log.info("Login accepted player "+wcToRemove.account+" waited("+(GameTimeController.getGameTicks()-wcToRemove.timestamp)+"ms)");
PlayerInGame pig = new PlayerInGame(par.getAccount());
sendPacket(pig);
wcToRemove.gameClient.setState(GameClientState.AUTHED);
wcToRemove.gameClient.setSessionId(wcToRemove.session);
CharSelectionInfo cl = new CharSelectionInfo(wcToRemove.account, wcToRemove.gameClient.getSessionId().playOkID1);
wcToRemove.gameClient.getConnection().sendPacket(cl);
wcToRemove.gameClient.setCharSelection(cl.getCharInfo());
}
else
{
_log.warning("session key is not correct. closing connection");
wcToRemove.gameClient.getConnection().sendPacket(new LoginFail(1));
wcToRemove.gameClient.closeNow();
}
_waitingClients.remove(wcToRemove);
}
break;
case 04:
KickPlayer kp = new KickPlayer(decrypt);
doKickPlayer(kp.getAccount());
break;
}
}
}
catch (UnknownHostException e)
{
if (Config.DEBUG) e.printStackTrace();
}
catch (IOException e)
{
_log.info("Deconnected from Login, Trying to reconnect:");
_log.info(e.toString());
}
finally
{
try { _loginSocket.close(); } catch (Exception e) {}
}
try
{
Thread.sleep(5000); // 5 seconds tempo.
}
catch(InterruptedException e)
{
//
}
}
}
public void addWaitingClientAndSendRequest(String acc, L2GameClient client, SessionKey key)
{
if(Config.DEBUG) System.out.println(key);
WaitingClient wc = new WaitingClient(acc, client, key);
synchronized(_waitingClients)
{
_waitingClients.add(wc);
}
PlayerAuthRequest par = new PlayerAuthRequest(acc,key);
try
{
sendPacket(par);
}
catch (IOException e)
{
_log.warning("Error while sending player auth request");
if (Config.DEBUG) e.printStackTrace();
}
}
public void removeWaitingClient(L2GameClient client)
{
WaitingClient toRemove = null;
synchronized(_waitingClients)
{
for(WaitingClient c :_waitingClients)
{
if(c.gameClient == client)
{
toRemove = c;
}
}
if(toRemove != null)
_waitingClients.remove(toRemove);
}
}
public void sendLogout(String account)
{
PlayerLogout pl = new PlayerLogout(account);
try
{
sendPacket(pl);
}
catch (IOException e)
{
_log.warning("Error while sending logout packet to login");
if (Config.DEBUG) e.printStackTrace();
}
}
public void addGameServerLogin(String account, L2GameClient client)
{
_accountsInGameServer.put(account, client);
}
public void sendAccessLevel(String account, int level)
{
ChangeAccessLevel cal = new ChangeAccessLevel(account, level);
try
{
sendPacket(cal);
}
catch (IOException e)
{
if (Config.DEBUG)
e.printStackTrace();
}
}
private String hexToString(byte[] hex)
{
return new BigInteger(hex).toString(16);
}
public void doKickPlayer(String account)
{
if(_accountsInGameServer.get(account) != null)
{
_accountsInGameServer.get(account).closeNow();
LoginServerThread.getInstance().sendLogout(account);
}
}
public static byte[] generateHex(int size)
{
byte [] array = new byte[size];
Rnd.nextBytes(array);
if (Config.DEBUG)_log.fine("Generated random String: \""+array+"\"");
return array;
}
/**
* @param sl
* @throws IOException
*/
private void sendPacket(GameServerBasePacket sl) throws IOException
{
byte[] data = sl.getContent();
NewCrypt.appendChecksum(data);
if (Config.DEBUG) _log.finest("[S]\n"+Util.printData(data));
data = _blowfish.crypt(data);
int len = data.length+2;
synchronized (_out) //avoids tow threads writing in the mean time
{
_out.write(len & 0xff);
_out.write(len >> 8 &0xff);
_out.write(data);
_out.flush();
}
}
/**
* @param maxPlayer The maxPlayer to set.
*/
public void setMaxPlayer(int maxPlayer)
{
sendServerStatus(ServerStatus.MAX_PLAYERS,maxPlayer);
_maxPlayer = maxPlayer;
}
/**
* @return Returns the maxPlayer.
*/
public int getMaxPlayer()
{
return _maxPlayer;
}
/**
* @param server_gm_only
*/
public void sendServerStatus(int id, int value)
{
ServerStatus ss = new ServerStatus();
ss.addAttribute(id,value);
try
{
sendPacket(ss);
}
catch (IOException e)
{
if (Config.DEBUG) e.printStackTrace();
}
}
/**
* @return
*/
public String getStatusString()
{
return ServerStatus.STATUS_STRING[_status];
}
/**
* @return
*/
public boolean isClockShown()
{
return Config.SERVER_LIST_CLOCK;
}
/**
* @return
*/
public boolean isBracketShown()
{
return Config.SERVER_LIST_BRACKET;
}
/**
* @return Returns the serverName.
*/
public String getServerName()
{
return _serverName;
}
public void setServerStatus(int status)
{
switch(status)
{
case ServerStatus.STATUS_AUTO:
sendServerStatus(ServerStatus.SERVER_LIST_STATUS,ServerStatus.STATUS_AUTO);
_status = status;
break;
case ServerStatus.STATUS_DOWN:
sendServerStatus(ServerStatus.SERVER_LIST_STATUS,ServerStatus.STATUS_DOWN);
_status = status;
break;
case ServerStatus.STATUS_FULL:
sendServerStatus(ServerStatus.SERVER_LIST_STATUS,ServerStatus.STATUS_FULL);
_status = status;
break;
case ServerStatus.STATUS_GM_ONLY:
sendServerStatus(ServerStatus.SERVER_LIST_STATUS,ServerStatus.STATUS_GM_ONLY);
_status = status;
break;
case ServerStatus.STATUS_GOOD:
sendServerStatus(ServerStatus.SERVER_LIST_STATUS,ServerStatus.STATUS_GOOD);
_status = status;
break;
case ServerStatus.STATUS_NORMAL:
sendServerStatus(ServerStatus.SERVER_LIST_STATUS,ServerStatus.STATUS_NORMAL);
_status = status;
break;
default:
throw new IllegalArgumentException("Status does not exists:"+status);
}
}
public static class SessionKey
{
public int playOkID1;
public int playOkID2;
public int loginOkID1;
public int loginOkID2;
public SessionKey(int loginOK1, int loginOK2, int playOK1, int playOK2)
{
playOkID1 = playOK1;
playOkID2 = playOK2;
loginOkID1 = loginOK1;
loginOkID2 = loginOK2;
}
@Override
public String toString()
{
return "PlayOk: "+playOkID1+" "+playOkID2+" LoginOk:"+loginOkID1+" "+loginOkID2;
}
}
private class WaitingClient
{
public int timestamp;
public String account;
public L2GameClient gameClient;
public SessionKey session;
public WaitingClient(String acc, L2GameClient client, SessionKey key)
{
account = acc;
timestamp = GameTimeController.getGameTicks();
gameClient = client;
session = key;
}
}
}