/*
* 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.FileNotFoundException;
import java.io.InputStream;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.spec.RSAKeyGenParameterSpec;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
import javolution.io.UTF8StreamReader;
import javolution.util.FastMap;
import javolution.xml.stream.XMLStreamConstants;
import javolution.xml.stream.XMLStreamException;
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;
/**
*
* @author KenM
*/
public class GameServerTable
{
private static Logger _log = Logger.getLogger(GameServerTable.class.getName());
private static GameServerTable _instance;
// Server Names Config
private static Map _serverNames = new FastMap();
// Game Server Table
private Map _gameServerTable = new FastMap().shared();
// RSA Config
private static final int KEYS_SIZE = 10;
private KeyPair[] _keyPairs;
public static void load() throws SQLException, GeneralSecurityException
{
synchronized (GameServerTable.class)
{
if (_instance == null)
{
_instance = new GameServerTable();
}
else
{
throw new IllegalStateException("Load can only be invoked a single time.");
}
}
}
public static GameServerTable getInstance()
{
return _instance;
}
public GameServerTable() throws SQLException, NoSuchAlgorithmException, InvalidAlgorithmParameterException
{
loadServerNames();
_log.info("Loaded " + _serverNames.size() + " server names");
loadRegisteredGameServers();
_log.info("Loaded " + _gameServerTable.size() + " registered Game Servers");
loadRSAKeys();
_log.info("Cached " + _keyPairs.length + " RSA keys for Game Server communication.");
}
private void loadRSAKeys() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException
{
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
RSAKeyGenParameterSpec spec = new RSAKeyGenParameterSpec(512, RSAKeyGenParameterSpec.F4);
keyGen.initialize(spec);
_keyPairs = new KeyPair[KEYS_SIZE];
for (int i = 0; i < KEYS_SIZE; i++)
{
_keyPairs[i] = keyGen.genKeyPair();
}
}
private void loadServerNames()
{
InputStream in = null;
try
{
in = new FileInputStream(new File(Config.DATAPACK_ROOT, "data/servername.xml"));
XMLStreamReaderImpl xpp = new XMLStreamReaderImpl();
xpp.setInput(new UTF8StreamReader().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);
}
}
}
}
catch (FileNotFoundException e)
{
_log.warning("servername.xml could not be loaded: file not found");
}
catch (XMLStreamException xppe)
{
_log.warning(getClass().getSimpleName() + ": " + xppe.getMessage());
}
finally
{
try
{
in.close();
}
catch (Exception e)
{
_log.warning(getClass().getSimpleName() + ": " + e.getMessage());
}
}
}
private void loadRegisteredGameServers() throws SQLException
{
Connection con = L2DatabaseFactory.getInstance().getConnection();
PreparedStatement statement = con.prepareStatement("SELECT * FROM gameservers");
ResultSet rset = statement.executeQuery();
int id;
while (rset.next())
{
id = rset.getInt("server_id");
_gameServerTable.put(id, new GameServerInfo(id, stringToHex(rset.getString("hexid"))));
}
rset.close();
statement.close();
L2DatabaseFactory.close(con);
}
public Map getRegisteredGameServers()
{
return _gameServerTable;
}
public GameServerInfo getRegisteredGameServerById(int id)
{
return _gameServerTable.get(id);
}
public boolean hasRegisteredGameServerOnId(int id)
{
return _gameServerTable.containsKey(id);
}
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;
}
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;
}
public void registerServerOnDB(GameServerInfo gsi)
{
this.registerServerOnDB(gsi.getHexId(), gsi.getId(), gsi.getExternalHost());
}
public void registerServerOnDB(byte[] hexId, int id, String externalHost)
{
Connection con = null;
try
{
con = L2DatabaseFactory.getInstance().getConnection();
PreparedStatement statement = con.prepareStatement("INSERT INTO gameservers (hexid,server_id,host) values (?,?,?)");
statement.setString(1, hexToString(hexId));
statement.setInt(2, id);
statement.setString(3, externalHost);
statement.executeUpdate();
statement.close();
this.register(id, new GameServerInfo(id, hexId));
}
catch (SQLException e)
{
_log.log(Level.SEVERE, "SQL error while saving gameserver.", e);
}
finally
{
L2DatabaseFactory.close(con);
}
}
public String getServerNameById(int id)
{
return getServerNames().get(id);
}
public Map getServerNames()
{
return _serverNames;
}
public KeyPair getKeyPair()
{
return _keyPairs[Rnd.nextInt(10)];
}
private byte[] stringToHex(String string)
{
return new BigInteger(string, 16).toByteArray();
}
private String hexToString(byte[] hex)
{
if (hex == null)
{
return "null";
}
return new BigInteger(hex).toString(16);
}
public static class GameServerInfo
{
// auth
private int _id;
private byte[] _hexId;
private boolean _isAuthed;
// status
private GameServerThread _gst;
private int _status;
// network
private ArrayList _addrs = new ArrayList(5);
private int _port;
// config
private boolean _isPvp = true;
private int _serverType;
private int _ageLimit;
private boolean _isShowingBrackets;
private int _maxPlayers;
public GameServerInfo(int id, byte[] hexId, GameServerThread gst)
{
_id = id;
_hexId = hexId;
_gst = gst;
_status = ServerStatus.STATUS_DOWN;
}
public GameServerInfo(int id, byte[] hexId)
{
this(id, hexId, null);
}
public void setId(int id)
{
_id = id;
}
public int getId()
{
return _id;
}
public byte[] getHexId()
{
return _hexId;
}
public void setAuthed(boolean isAuthed)
{
_isAuthed = isAuthed;
}
public boolean isAuthed()
{
return _isAuthed;
}
public void setGameServerThread(GameServerThread gst)
{
_gst = gst;
}
public GameServerThread getGameServerThread()
{
return _gst;
}
public void setStatus(int status)
{
_status = status;
}
public int getStatus()
{
return _status;
}
public int getCurrentPlayerCount()
{
if (_gst == null)
return 0;
return _gst.getPlayerCount();
}
public String getExternalHost()
{
try
{
return getServerAddress(InetAddress.getByName("0.0.0.0"));
}
catch (Exception e)
{
}
return null;
}
public int getPort()
{
return _port;
}
public void setPort(int port)
{
_port = port;
}
public void setMaxPlayers(int maxPlayers)
{
_maxPlayers = maxPlayers;
}
public int getMaxPlayers()
{
return _maxPlayers;
}
public boolean isPvp()
{
return _isPvp;
}
public void setAgeLimit(int val)
{
_ageLimit = val;
}
public int getAgeLimit()
{
return _ageLimit;
}
public void setServerType(int val)
{
_serverType = val;
}
public int getServerType()
{
return _serverType;
}
public void setShowingBrackets(boolean val)
{
_isShowingBrackets = val;
}
public boolean isShowingBrackets()
{
return _isShowingBrackets;
}
public void setDown()
{
setAuthed(false);
setPort(0);
setGameServerThread(null);
setStatus(ServerStatus.STATUS_DOWN);
}
public void addServerAddress(String subnet, String addr) throws UnknownHostException
{
_addrs.add(new GameServerAddress(subnet, addr));
}
public String getServerAddress(InetAddress addr)
{
for (GameServerAddress a : _addrs)
{
if (a.equals(addr))
return a.getServerAddress();
}
return null; // should not happens
}
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;
}
public void clearServerAddresses()
{
_addrs.clear();
}
private class GameServerAddress extends IPSubnet
{
private String _serverAddress;
public GameServerAddress(String subnet, String address) throws UnknownHostException
{
super(subnet);
_serverAddress = address;
}
public String getServerAddress()
{
return _serverAddress;
}
@Override
public String toString()
{
return _serverAddress + super.toString();
}
}
}
}