forked from MagicBane/Server
Initial Repository Push
This commit is contained in:
@@ -0,0 +1,524 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2022
|
||||
// www.magicbane.com
|
||||
|
||||
|
||||
package engine.server.login;
|
||||
|
||||
import com.zaxxer.hikari.HikariConfig;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import engine.Enum;
|
||||
import engine.gameManager.*;
|
||||
import engine.job.JobScheduler;
|
||||
import engine.jobs.CSessionCleanupJob;
|
||||
import engine.net.Network;
|
||||
import engine.net.client.ClientConnection;
|
||||
import engine.net.client.ClientConnectionManager;
|
||||
import engine.net.client.Protocol;
|
||||
import engine.net.client.msg.login.ServerStatusMsg;
|
||||
import engine.net.client.msg.login.VersionInfoMsg;
|
||||
import engine.objects.*;
|
||||
import engine.server.MBServerStatics;
|
||||
import engine.util.ByteUtils;
|
||||
import engine.util.ThreadUtils;
|
||||
import org.pmw.tinylog.Configurator;
|
||||
import org.pmw.tinylog.Level;
|
||||
import org.pmw.tinylog.Logger;
|
||||
import org.pmw.tinylog.labelers.TimestampLabeler;
|
||||
import org.pmw.tinylog.policies.StartupPolicy;
|
||||
import org.pmw.tinylog.writers.RollingFileWriter;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.InetAddress;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Iterator;
|
||||
|
||||
import static java.lang.System.exit;
|
||||
|
||||
public class LoginServer {
|
||||
|
||||
|
||||
// Instance variables
|
||||
|
||||
private VersionInfoMsg versionInfoMessage;
|
||||
public static HikariDataSource connectionPool = null;
|
||||
public static int population = 0;
|
||||
public static boolean worldServerRunning = false;
|
||||
public static boolean loginServerRunning = false;
|
||||
|
||||
public static ServerStatusMsg serverStatusMsg = new ServerStatusMsg(0, (byte) 1);
|
||||
|
||||
// This is the entrypoint for the MagicBane Login Server when
|
||||
// it is executed by the command line scripts. The fun begins here!
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
LoginServer loginServer;
|
||||
|
||||
// Initialize TinyLog logger with our own format
|
||||
|
||||
Configurator.defaultConfig()
|
||||
.addWriter(new RollingFileWriter("logs/login/login.txt", 30, new TimestampLabeler(), new StartupPolicy()))
|
||||
.level(Level.DEBUG)
|
||||
.formatPattern("{level} {date:yyyy-MM-dd HH:mm:ss.SSS} [{thread}] {class}.{method}({line}) : {message}")
|
||||
.activate();
|
||||
|
||||
try {
|
||||
|
||||
// Configure the the Login Server
|
||||
|
||||
loginServer = new LoginServer();
|
||||
ConfigManager.loginServer = loginServer;
|
||||
ConfigManager.handler = new LoginServerMsgHandler(loginServer);
|
||||
|
||||
ConfigManager.serverType = Enum.ServerType.LOGINSERVER;
|
||||
|
||||
if (ConfigManager.init() == false) {
|
||||
Logger.error("ABORT! Missing config entry!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Start the Login Server
|
||||
|
||||
loginServer.init();
|
||||
loginServer.exec();
|
||||
|
||||
exit(0);
|
||||
|
||||
} catch (Exception e) {
|
||||
Logger.error(e);
|
||||
e.printStackTrace();
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Mainline execution loop for the login server.
|
||||
|
||||
private void exec() {
|
||||
|
||||
|
||||
LocalDateTime nextCacheTime = LocalDateTime.now();
|
||||
LocalDateTime nextServerTime = LocalDateTime.now();
|
||||
LocalDateTime nextDatabaseTime = LocalDateTime.now();
|
||||
|
||||
loginServerRunning = true;
|
||||
|
||||
while (true) {
|
||||
|
||||
// Invalidate cache for players driven by forum
|
||||
// and stored procedure forum_link_pass()
|
||||
|
||||
try {
|
||||
|
||||
// Run cache routine right away if requested.
|
||||
|
||||
File cacheFile = new File("cacheInvalid");
|
||||
|
||||
|
||||
if (cacheFile.exists() == true) {
|
||||
nextCacheTime = LocalDateTime.now();
|
||||
Files.deleteIfExists(Paths.get("cacheInvalid"));
|
||||
}
|
||||
|
||||
if (LocalDateTime.now().isAfter(nextCacheTime)) {
|
||||
invalidateCacheList();
|
||||
nextCacheTime = LocalDateTime.now().plusSeconds(30);
|
||||
}
|
||||
|
||||
if (LocalDateTime.now().isAfter(nextServerTime)) {
|
||||
checkServerHealth();
|
||||
nextServerTime = LocalDateTime.now().plusSeconds(1);
|
||||
}
|
||||
|
||||
if (LocalDateTime.now().isAfter(nextDatabaseTime)) {
|
||||
String pop = SimulationManager.getPopulationString();
|
||||
Logger.info("Keepalive: " + pop);
|
||||
nextDatabaseTime = LocalDateTime.now().plusMinutes(30);
|
||||
}
|
||||
|
||||
ThreadUtils.sleep(100);
|
||||
} catch (Exception e) {
|
||||
Logger.error(e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Constructor
|
||||
|
||||
public LoginServer() {
|
||||
|
||||
}
|
||||
|
||||
private boolean init() {
|
||||
|
||||
// Initialize Application Protocol
|
||||
|
||||
Protocol.initProtocolLookup();
|
||||
|
||||
// Configure the VersionInfoMsgs:
|
||||
|
||||
this.versionInfoMessage = new VersionInfoMsg(MBServerStatics.PCMajorVer,
|
||||
MBServerStatics.PCMinorVer);
|
||||
|
||||
Logger.info("Initializing Database Pool");
|
||||
initDatabasePool();
|
||||
|
||||
Logger.info("Initializing Database layer");
|
||||
initDatabaseLayer();
|
||||
|
||||
Logger.info("Initializing Network");
|
||||
Network.init();
|
||||
|
||||
Logger.info("Initializing Client Connection Manager");
|
||||
initClientConnectionManager();
|
||||
|
||||
// instantiate AccountManager
|
||||
Logger.info("Initializing SessionManager.");
|
||||
|
||||
// Sets cross server behavior
|
||||
SessionManager.setCrossServerBehavior(0);
|
||||
|
||||
// activate powers manager
|
||||
Logger.info("Initializing PowersManager.");
|
||||
PowersManager.initPowersManager(false);
|
||||
|
||||
RuneBaseAttribute.LoadAllAttributes();
|
||||
RuneBase.LoadAllRuneBases();
|
||||
BaseClass.LoadAllBaseClasses();
|
||||
Race.loadAllRaces();
|
||||
RuneBaseEffect.LoadRuneBaseEffects();
|
||||
|
||||
Logger.info("Initializing Blueprint data.");
|
||||
Blueprint.loadAllBlueprints();
|
||||
|
||||
Logger.info("Loading Kits");
|
||||
DbManager.KitQueries.GET_ALL_KITS();
|
||||
|
||||
Logger.info("Initializing ItemBase data.");
|
||||
ItemBase.loadAllItemBases();
|
||||
|
||||
Logger.info("Initializing Race data");
|
||||
Enum.RaceType.initRaceTypeTables();
|
||||
Race.loadAllRaces();
|
||||
|
||||
Logger.info("Initializing Errant Guild");
|
||||
Guild.CreateErrantGuild();
|
||||
|
||||
Logger.info("Loading All Guilds");
|
||||
DbManager.GuildQueries.GET_ALL_GUILDS();
|
||||
|
||||
|
||||
Logger.info("***Boot Successful***");
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean initDatabaseLayer() {
|
||||
|
||||
// Try starting a GOM <-> DB connection.
|
||||
try {
|
||||
|
||||
Logger.info("Configuring GameObjectManager to use Database: '"
|
||||
+ ConfigManager.MB_DATABASE_NAME.getValue() + "' on "
|
||||
+ ConfigManager.MB_DATABASE_ADDRESS.getValue() + ':'
|
||||
+ ConfigManager.MB_DATABASE_PORT.getValue());
|
||||
|
||||
DbManager.configureDatabaseLayer();
|
||||
|
||||
} catch (Exception e) {
|
||||
Logger.error(e.getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
PreparedStatementShared.submitPreparedStatementsCleaningJob();
|
||||
|
||||
if (MBServerStatics.DB_DEBUGGING_ON_BY_DEFAULT) {
|
||||
PreparedStatementShared.enableDebugging();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void removeClient(ClientConnection conn) {
|
||||
if (conn == null) {
|
||||
Logger.info(
|
||||
"ClientConnection null in removeClient.");
|
||||
return;
|
||||
}
|
||||
String key = ByteUtils.byteArrayToSafeStringHex(conn
|
||||
.getSecretKeyBytes());
|
||||
|
||||
CSessionCleanupJob cscj = new CSessionCleanupJob(key);
|
||||
|
||||
JobScheduler.getInstance().scheduleJob(cscj,
|
||||
MBServerStatics.SESSION_CLEANUP_TIMER_MS);
|
||||
}
|
||||
|
||||
private void initClientConnectionManager() {
|
||||
|
||||
try {
|
||||
|
||||
String name = ConfigManager.MB_WORLD_NAME.getValue();
|
||||
|
||||
// Find publicIP address for use in worldserver response
|
||||
// message. Sending the client to an unroutable address
|
||||
// doesn't work so well.
|
||||
|
||||
URL whatismyip = new URL("http://checkip.amazonaws.com");
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(
|
||||
whatismyip.openStream()));
|
||||
ConfigManager.MB_PUBLIC_ADDR.setValue(in.readLine());
|
||||
Logger.info("Public address: " + ConfigManager.MB_PUBLIC_ADDR.getValue());
|
||||
|
||||
Logger.info("Magicbane network config: " + ConfigManager.MB_BIND_ADDR.getValue() + ":" + ConfigManager.MB_LOGIN_PORT.getValue());
|
||||
|
||||
InetAddress addy = InetAddress.getByName(ConfigManager.MB_BIND_ADDR.getValue());
|
||||
int port = Integer.parseInt(ConfigManager.MB_LOGIN_PORT.getValue());
|
||||
|
||||
ClientConnectionManager connectionManager = new ClientConnectionManager(name + ".ClientConnMan", addy,
|
||||
port);
|
||||
connectionManager.startup();
|
||||
|
||||
} catch (IOException e) {
|
||||
Logger.error(e.toString());
|
||||
}
|
||||
}
|
||||
/*
|
||||
* message handlers (relay)
|
||||
*/
|
||||
|
||||
// ==============================
|
||||
// Support Functions
|
||||
// ==============================
|
||||
|
||||
public VersionInfoMsg getDefaultVersionInfo() {
|
||||
return versionInfoMessage;
|
||||
}
|
||||
|
||||
//this updates a server being up or down without resending the entire char select screen.
|
||||
public void updateServersForAll(boolean isRunning) {
|
||||
|
||||
try {
|
||||
|
||||
Iterator<ClientConnection> i = SessionManager.getAllActiveClientConnections().iterator();
|
||||
|
||||
while (i.hasNext()) {
|
||||
|
||||
ClientConnection clientConnection = i.next();
|
||||
|
||||
if (clientConnection == null)
|
||||
continue;
|
||||
|
||||
Account ac = clientConnection.getAccount();
|
||||
|
||||
if (ac == null)
|
||||
continue;
|
||||
|
||||
boolean isUp = isRunning;
|
||||
|
||||
|
||||
if (MBServerStatics.worldAccessLevel.ordinal() > ac.status.ordinal())
|
||||
isUp = false;
|
||||
|
||||
LoginServer.serverStatusMsg.setServerID(MBServerStatics.worldMapID);
|
||||
LoginServer.serverStatusMsg.setIsUp(isUp ? (byte) 1 : (byte) 0);
|
||||
clientConnection.sendMsg(LoginServer.serverStatusMsg);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Logger.error(e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void checkServerHealth() {
|
||||
|
||||
// Check if worldserver is running
|
||||
|
||||
if (!isPortInUse(Integer.parseInt(ConfigManager.MB_WORLD_PORT.getValue()))) {
|
||||
worldServerRunning = false;
|
||||
population = 0;
|
||||
updateServersForAll(worldServerRunning);
|
||||
return;
|
||||
}
|
||||
|
||||
// Worldserver is running and writes a polling file.
|
||||
// Read the current population count from the server and
|
||||
// update player displays accordingly.
|
||||
|
||||
worldServerRunning = true;
|
||||
population = readPopulationFile();
|
||||
updateServersForAll(worldServerRunning);
|
||||
|
||||
}
|
||||
|
||||
private void initDatabasePool() {
|
||||
|
||||
HikariConfig config = new HikariConfig();
|
||||
|
||||
config.setMaximumPoolSize(33); // (16 cores 1 spindle)
|
||||
|
||||
config.setJdbcUrl("jdbc:mysql://" + ConfigManager.MB_DATABASE_ADDRESS.getValue() +
|
||||
":" + ConfigManager.MB_DATABASE_PORT.getValue() + "/" +
|
||||
ConfigManager.MB_DATABASE_NAME.getValue());
|
||||
config.setUsername(ConfigManager.MB_DATABASE_USER.getValue());
|
||||
config.setPassword(ConfigManager.MB_DATABASE_PASS.getValue());
|
||||
config.addDataSourceProperty("characterEncoding", "utf8");
|
||||
config.addDataSourceProperty("cachePrepStmts", "true");
|
||||
config.addDataSourceProperty("prepStmtCacheSize", "250");
|
||||
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
|
||||
|
||||
connectionPool = new HikariDataSource(config); // setup the connection pool
|
||||
|
||||
Logger.info("local database connection configured");
|
||||
}
|
||||
|
||||
public void invalidateCacheList() {
|
||||
|
||||
int objectUUID;
|
||||
String objectType;
|
||||
|
||||
try (Connection connection = connectionPool.getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement("SELECT * FROM `login_cachelist`");
|
||||
ResultSet rs = statement.executeQuery()) {
|
||||
|
||||
while (rs.next()) {
|
||||
|
||||
objectUUID = rs.getInt("UID");
|
||||
objectType = rs.getString("type");
|
||||
|
||||
Logger.info("INVALIDATED : " + objectType + " UUID: " + objectUUID);
|
||||
|
||||
switch (objectType) {
|
||||
|
||||
case "account":
|
||||
DbManager.removeFromCache(Enum.GameObjectType.Account, objectUUID);
|
||||
break;
|
||||
case "character":
|
||||
DbManager.removeFromCache(Enum.GameObjectType.PlayerCharacter, objectUUID);
|
||||
PlayerCharacter player = (PlayerCharacter) DbManager.getObject(Enum.GameObjectType.PlayerCharacter, objectUUID);
|
||||
PlayerCharacter.initializePlayer(player);
|
||||
player.getAccount().characterMap.replace(player.getObjectUUID(), player);
|
||||
Logger.info("Player active state is : " + player.isActive());
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} catch (SQLException e) {
|
||||
Logger.info(e.toString());
|
||||
}
|
||||
|
||||
// clear the db table
|
||||
|
||||
try (Connection connection = connectionPool.getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement("DELETE FROM `login_cachelist`")) {
|
||||
|
||||
statement.execute();
|
||||
|
||||
} catch (SQLException e) {
|
||||
Logger.info(e.toString());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static boolean getActiveBaneQuery(PlayerCharacter playerCharacter) {
|
||||
|
||||
boolean outStatus = false;
|
||||
|
||||
// char has never logged on so cannot have dropped a bane
|
||||
|
||||
if (playerCharacter.getHash() == null)
|
||||
return outStatus;
|
||||
|
||||
// query data warehouse for unresolved bane with this character
|
||||
|
||||
try (Connection connection = connectionPool.getConnection();
|
||||
PreparedStatement statement = buildQueryActiveBaneStatement(connection, playerCharacter);
|
||||
ResultSet rs = statement.executeQuery()) {
|
||||
|
||||
while (rs.next()) {
|
||||
|
||||
outStatus = true;
|
||||
}
|
||||
|
||||
} catch (SQLException e) {
|
||||
Logger.error(e.toString());
|
||||
}
|
||||
|
||||
return outStatus;
|
||||
}
|
||||
|
||||
private static PreparedStatement buildQueryActiveBaneStatement(Connection connection, PlayerCharacter playerCharacter) throws SQLException {
|
||||
PreparedStatement outStatement;
|
||||
String queryString = "SELECT `city_id` FROM `warehouse_banehistory` WHERE `char_id` = ? AND `RESOLUTION` = 'PENDING'";
|
||||
outStatement = connection.prepareStatement(queryString);
|
||||
outStatement.setString(1, playerCharacter.getHash());
|
||||
return outStatement;
|
||||
|
||||
}
|
||||
|
||||
public static boolean isPortInUse(int port) {
|
||||
|
||||
ProcessBuilder builder = new ProcessBuilder("/bin/bash", "-c", "lsof -i tcp:" + port + " | tail -n +2 | awk '{print $2}'");
|
||||
builder.redirectErrorStream(true);
|
||||
Process process = null;
|
||||
String line = null;
|
||||
boolean portInUse = false;
|
||||
|
||||
try {
|
||||
process = builder.start();
|
||||
|
||||
InputStream is = process.getInputStream();
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
|
||||
|
||||
while ((line = reader.readLine()) != null) {
|
||||
portInUse = true;
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return portInUse;
|
||||
}
|
||||
|
||||
private int readPopulationFile() {
|
||||
|
||||
ProcessBuilder builder = new ProcessBuilder("/bin/bash", "-c", "cat " + MBServerStatics.DEFAULT_DATA_DIR + ConfigManager.MB_WORLD_NAME.getValue().replaceAll("'","") + ".pop");
|
||||
builder.redirectErrorStream(true);
|
||||
Process process = null;
|
||||
String line = null;
|
||||
int population = 0;
|
||||
|
||||
try {
|
||||
|
||||
process = builder.start();
|
||||
|
||||
InputStream is = process.getInputStream();
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
|
||||
|
||||
while ((line = reader.readLine()) != null) {
|
||||
population = Integer.parseInt(line);
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return 0;
|
||||
}
|
||||
|
||||
return population;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user