/*
* 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 com.l2jserver.loginserver;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.spec.RSAKeyGenParameterSpec;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Logger;
import javolution.io.UTF8StreamReader;
import javolution.xml.stream.XMLStreamConstants;
import javolution.xml.stream.XMLStreamReaderImpl;
import com.l2jserver.Config;
import com.l2jserver.L2DatabaseFactory;
import com.l2jserver.loginserver.network.gameserverpackets.ServerStatus;
import com.l2jserver.util.IPSubnet;
import com.l2jserver.util.Rnd;
/**
* The Class GameServerTable loads the game server names and initialize the game server tables.
* @author KenM, Zoey76
*/
public final class GameServerTable
{
private static final Logger _log = Logger.getLogger(GameServerTable.class.getName());
// Server Names Config
private static final Map _serverNames = new HashMap<>();
// Game Server Table
private static final Map _gameServerTable = new HashMap<>();
// RSA Config
private static final int KEYS_SIZE = 10;
private KeyPair[] _keyPairs;
/**
* Instantiates a new game server table.
*/
public GameServerTable()
{
loadGameServerNames();
_log.info(getClass().getSimpleName() + ": Loaded " + _serverNames.size() + " server names");
loadRegisteredGameServers();
_log.info(getClass().getSimpleName() + ": Loaded " + _gameServerTable.size() + " registered Game Servers");
initRSAKeys();
_log.info(getClass().getSimpleName() + ": Cached " + _keyPairs.length + " RSA keys for Game Server communication.");
}
/**
* Load game server names.
*/
private void loadGameServerNames()
{
final File xml = new File(Config.DATAPACK_ROOT, "data/servername.xml");
try (InputStream in = new FileInputStream(xml);
UTF8StreamReader utf8 = new UTF8StreamReader())
{
final XMLStreamReaderImpl xpp = new XMLStreamReaderImpl();
xpp.setInput(utf8.setInput(in));
for (int e = xpp.getEventType(); e != XMLStreamConstants.END_DOCUMENT; e = xpp.next())
{
if (e == XMLStreamConstants.START_ELEMENT)
{
if (xpp.getLocalName().toString().equals("server"))
{
Integer id = Integer.valueOf(xpp.getAttributeValue(null, "id").toString());
String name = xpp.getAttributeValue(null, "name").toString();
_serverNames.put(id, name);
}
}
}
xpp.close();
}
catch (Exception e)
{
_log.info(getClass().getSimpleName() + ": Cannot load " + xml.getAbsolutePath() + "!");
}
}
/**
* Inits the RSA keys.
*/
private void initRSAKeys()
{
try
{
final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(new RSAKeyGenParameterSpec(512, RSAKeyGenParameterSpec.F4));
_keyPairs = new KeyPair[KEYS_SIZE];
for (int i = 0; i < KEYS_SIZE; i++)
{
_keyPairs[i] = keyGen.genKeyPair();
}
}
catch (Exception e)
{
_log.severe(getClass().getSimpleName() + ": Error loading RSA keys for Game Server communication!");
}
}
/**
* Load registered game servers.
*/
private void loadRegisteredGameServers()
{
Connection con = null;
try
{
con = L2DatabaseFactory.getInstance().getConnection();
try (Statement ps = con.createStatement();
ResultSet rs = ps.executeQuery("SELECT * FROM gameservers"))
{
int id;
while (rs.next())
{
id = rs.getInt("server_id");
_gameServerTable.put(id, new GameServerInfo(id, stringToHex(rs.getString("hexid"))));
}
}
}
catch (Exception e)
{
_log.severe(getClass().getSimpleName() + ": Error loading registered game servers!");
}
finally
{
L2DatabaseFactory.close(con);
}
}
/**
* Gets the registered game servers.
* @return the registered game servers
*/
public Map getRegisteredGameServers()
{
return _gameServerTable;
}
/**
* Gets the registered game server by id.
* @param id the game server Id
* @return the registered game server by id
*/
public GameServerInfo getRegisteredGameServerById(int id)
{
return _gameServerTable.get(id);
}
/**
* Checks for registered game server on id.
* @param id the id
* @return true, if successful
*/
public boolean hasRegisteredGameServerOnId(int id)
{
return _gameServerTable.containsKey(id);
}
/**
* Register with first available id.
* @param gsi the game server information DTO
* @return true, if successful
*/
public boolean registerWithFirstAvaliableId(GameServerInfo gsi)
{
// avoid two servers registering with the same "free" id
synchronized (_gameServerTable)
{
for (Entry entry : _serverNames.entrySet())
{
if (!_gameServerTable.containsKey(entry.getKey()))
{
_gameServerTable.put(entry.getKey(), gsi);
gsi.setId(entry.getKey());
return true;
}
}
}
return false;
}
/**
* Register a game server.
* @param id the id
* @param gsi the gsi
* @return true, if successful
*/
public boolean register(int id, GameServerInfo gsi)
{
// avoid two servers registering with the same id
synchronized (_gameServerTable)
{
if (!_gameServerTable.containsKey(id))
{
_gameServerTable.put(id, gsi);
gsi.setId(id);
return true;
}
}
return false;
}
/**
* Wrapper method.
* @param gsi the game server info DTO.
*/
public void registerServerOnDB(GameServerInfo gsi)
{
registerServerOnDB(gsi.getHexId(), gsi.getId(), gsi.getExternalHost());
}
/**
* Register server on db.
* @param hexId the hex id
* @param id the id
* @param externalHost the external host
*/
public void registerServerOnDB(byte[] hexId, int id, String externalHost)
{
Connection con = null;
try
{
con = L2DatabaseFactory.getInstance().getConnection();
try (PreparedStatement ps = con.prepareStatement("INSERT INTO gameservers (hexid,server_id,host) values (?,?,?)"))
{
ps.setString(1, hexToString(hexId));
ps.setInt(2, id);
ps.setString(3, externalHost);
ps.executeUpdate();
}
register(id, new GameServerInfo(id, hexId));
}
catch (Exception e)
{
_log.severe(getClass().getSimpleName() + ": Error while saving gameserver!");
}
finally
{
L2DatabaseFactory.close(con);
}
}
/**
* Gets the server name by id.
* @param id the id
* @return the server name by id
*/
public String getServerNameById(int id)
{
return _serverNames.get(id);
}
/**
* Gets the server names.
* @return the game server names map.
*/
public Map getServerNames()
{
return _serverNames;
}
/**
* Gets the key pair.
* @return a random key pair.
*/
public KeyPair getKeyPair()
{
return _keyPairs[Rnd.nextInt(10)];
}
/**
* String to hex.
* @param string the string to convert.
* @return return the hex representation.
*/
private byte[] stringToHex(String string)
{
return new BigInteger(string, 16).toByteArray();
}
/**
* Hex to string.
* @param hex the hex value to convert.
* @return the string representation.
*/
private String hexToString(byte[] hex)
{
if (hex == null)
{
return "null";
}
return new BigInteger(hex).toString(16);
}
/**
* The Class GameServerInfo.
*/
public static class GameServerInfo
{
// auth
private int _id;
private final byte[] _hexId;
private boolean _isAuthed;
// status
private GameServerThread _gst;
private int _status;
// network
private final ArrayList _addrs = new ArrayList<>(5);
private int _port;
// config
private final boolean _isPvp = true;
private int _serverType;
private int _ageLimit;
private boolean _isShowingBrackets;
private int _maxPlayers;
/**
* Instantiates a new game server info.
* @param id the id
* @param hexId the hex id
* @param gst the gst
*/
public GameServerInfo(int id, byte[] hexId, GameServerThread gst)
{
_id = id;
_hexId = hexId;
_gst = gst;
_status = ServerStatus.STATUS_DOWN;
}
/**
* Instantiates a new game server info.
* @param id the id
* @param hexId the hex id
*/
public GameServerInfo(int id, byte[] hexId)
{
this(id, hexId, null);
}
/**
* Sets the id.
* @param id the new id
*/
public void setId(int id)
{
_id = id;
}
/**
* Gets the id.
* @return the id
*/
public int getId()
{
return _id;
}
/**
* Gets the hex id.
* @return the hex id
*/
public byte[] getHexId()
{
return _hexId;
}
/**
* Sets the authed.
* @param isAuthed the new authed
*/
public void setAuthed(boolean isAuthed)
{
_isAuthed = isAuthed;
}
/**
* Checks if is authed.
* @return true, if is authed
*/
public boolean isAuthed()
{
return _isAuthed;
}
/**
* Sets the game server thread.
* @param gst the new game server thread
*/
public void setGameServerThread(GameServerThread gst)
{
_gst = gst;
}
/**
* Gets the game server thread.
* @return the game server thread
*/
public GameServerThread getGameServerThread()
{
return _gst;
}
/**
* Sets the status.
* @param status the new status
*/
public void setStatus(int status)
{
_status = status;
}
/**
* Gets the status.
* @return the status
*/
public int getStatus()
{
return _status;
}
/**
* Gets the current player count.
* @return the current player count
*/
public int getCurrentPlayerCount()
{
if (_gst == null)
{
return 0;
}
return _gst.getPlayerCount();
}
/**
* Gets the external host.
* @return the external host
*/
public String getExternalHost()
{
try
{
return getServerAddress(InetAddress.getByName("0.0.0.0"));
}
catch (Exception e)
{
}
return null;
}
/**
* Gets the port.
* @return the port
*/
public int getPort()
{
return _port;
}
/**
* Sets the port.
* @param port the new port
*/
public void setPort(int port)
{
_port = port;
}
/**
* Sets the max players.
* @param maxPlayers the new max players
*/
public void setMaxPlayers(int maxPlayers)
{
_maxPlayers = maxPlayers;
}
/**
* Gets the max players.
* @return the max players
*/
public int getMaxPlayers()
{
return _maxPlayers;
}
/**
* Checks if is pvp.
* @return true, if is pvp
*/
public boolean isPvp()
{
return _isPvp;
}
/**
* Sets the age limit.
* @param val the new age limit
*/
public void setAgeLimit(int val)
{
_ageLimit = val;
}
/**
* Gets the age limit.
* @return the age limit
*/
public int getAgeLimit()
{
return _ageLimit;
}
/**
* Sets the server type.
* @param val the new server type
*/
public void setServerType(int val)
{
_serverType = val;
}
/**
* Gets the server type.
* @return the server type
*/
public int getServerType()
{
return _serverType;
}
/**
* Sets the showing brackets.
* @param val the new showing brackets
*/
public void setShowingBrackets(boolean val)
{
_isShowingBrackets = val;
}
/**
* Checks if is showing brackets.
* @return true, if is showing brackets
*/
public boolean isShowingBrackets()
{
return _isShowingBrackets;
}
/**
* Sets the down.
*/
public void setDown()
{
setAuthed(false);
setPort(0);
setGameServerThread(null);
setStatus(ServerStatus.STATUS_DOWN);
}
/**
* Adds the server address.
* @param subnet the subnet
* @param addr the addr
* @throws UnknownHostException the unknown host exception
*/
public void addServerAddress(String subnet, String addr) throws UnknownHostException
{
_addrs.add(new GameServerAddress(subnet, addr));
}
/**
* Gets the server address.
* @param addr the addr
* @return the server address
*/
public String getServerAddress(InetAddress addr)
{
for (GameServerAddress a : _addrs)
{
if (a.equals(addr))
{
return a.getServerAddress();
}
}
return null; // should not happens
}
/**
* Gets the server addresses.
* @return the server addresses
*/
public String[] getServerAddresses()
{
String[] result = new String[_addrs.size()];
for (int i = 0; i < result.length; i++)
{
result[i] = _addrs.get(i).toString();
}
return result;
}
/**
* Clear server addresses.
*/
public void clearServerAddresses()
{
_addrs.clear();
}
/**
* The Class GameServerAddress.
*/
private class GameServerAddress extends IPSubnet
{
private final String _serverAddress;
/**
* Instantiates a new game server address.
* @param subnet the subnet
* @param address the address
* @throws UnknownHostException the unknown host exception
*/
public GameServerAddress(String subnet, String address) throws UnknownHostException
{
super(subnet);
_serverAddress = address;
}
/**
* Gets the server address.
* @return the server address
*/
public String getServerAddress()
{
return _serverAddress;
}
@Override
public String toString()
{
return _serverAddress + super.toString();
}
}
}
/**
* Gets the single instance of GameServerTable.
* @return single instance of GameServerTable
*/
public static GameServerTable getInstance()
{
return SingletonHolder._instance;
}
/**
* The Class SingletonHolder.
*/
private static class SingletonHolder
{
protected static final GameServerTable _instance = new GameServerTable();
}
}