PSF-BotServer/pslogin/src/main/scala/PsLogin.scala
2016-07-26 21:48:58 -04:00

188 lines
6.9 KiB
Scala

// Copyright (c) 2016 PSForever.net to present
import java.net.InetAddress
import java.io.File
import akka.actor.{ActorSystem, Props}
import ch.qos.logback.classic.LoggerContext
import ch.qos.logback.classic.joran.JoranConfigurator
import ch.qos.logback.core.joran.spi.JoranException
import ch.qos.logback.core.status._
import ch.qos.logback.core.util.StatusPrinter
import com.typesafe.config.ConfigFactory
import net.psforever.crypto.CryptoInterface
import org.slf4j
import org.fusesource.jansi.Ansi._
import org.fusesource.jansi.Ansi.Color._
import scala.collection.JavaConverters._
import scala.concurrent.Await
import scala.concurrent.duration._
object PsLogin {
private val logger = org.log4s.getLogger
def banner() : Unit = {
println(ansi().fgBright(BLUE).a(""" ___ ________"""))
println(ansi().fgBright(BLUE).a(""" / _ \/ __/ __/__ _______ _ _____ ____"""))
println(ansi().fgBright(MAGENTA).a(""" / ___/\ \/ _// _ \/ __/ -_) |/ / -_) __/"""))
println(ansi().fgBright(RED).a("""/_/ /___/_/ \___/_/ \__/|___/\__/_/""").reset())
println(""" Login Server - PSForever Project""")
println(""" http://psforever.net""")
println
}
/** Grabs the most essential system information and returns it as a preformatted string */
def systemInformation : String = {
s"""|~~~ System Information ~~~
|${System.getProperty("os.name")} (v. ${System.getProperty("os.version")}, ${System.getProperty("os.arch")})
|${System.getProperty("java.vm.name")} (build ${System.getProperty("java.version")}), ${System.getProperty("java.vendor")} - ${System.getProperty("java.vendor.url")}
""".stripMargin
}
/** Used to enumerate all of the Java properties. Used in testing only */
def enumerateAllProperties() : Unit = {
val props = System.getProperties
val enums = props.propertyNames()
while(enums.hasMoreElements) {
val key = enums.nextElement.toString
System.out.println(key + " : " + props.getProperty(key))
}
}
/**
* Checks the current logger context
* @param context SLF4J logger context
* @return Boolean return true if context has errors
*/
def loggerHasErrors(context : LoggerContext) = {
val statusUtil = new StatusUtil(context)
statusUtil.getHighestLevel(0) >= Status.WARN
}
/** Loads the logging configuration and starts logging */
def initializeLogging(logfile : String) : Unit = {
// assume SLF4J is bound to logback in the current environment
val lc = slf4j.LoggerFactory.getILoggerFactory.asInstanceOf[LoggerContext]
try {
val configurator = new JoranConfigurator()
configurator.setContext(lc)
// reset any loaded settings
lc.reset()
configurator.doConfigure(logfile)
}
catch {
case je : JoranException => ;
}
if(loggerHasErrors(lc)) {
println("Loading log settings failed")
StatusPrinter.printInCaseOfErrorsOrWarnings(lc)
sys.exit(1)
}
}
def parseArgs(args : Array[String]) : Unit = {
if(args.length == 1) {
LoginConfig.serverIpAddress = InetAddress.getByName(args{0})
}
else {
LoginConfig.serverIpAddress = InetAddress.getLocalHost
}
}
def main(args : Array[String]) : Unit = {
// Early start up
banner()
println(systemInformation)
// Config directory
// Assume a default of the current directory
var configDirectory = "config"
// This is defined when we are running from SBT pack
if(System.getProperty("prog.home") != null) {
configDirectory = System.getProperty("prog.home") + File.separator + "config"
}
initializeLogging(configDirectory + File.separator + "logback.xml")
parseArgs(args)
/** Initialize the PSCrypto native library
*
* PSCrypto provides PlanetSide specific crypto that is required to communicate with it.
* It has to be distributed as a native library because there is no Scala version of the required
* cryptographic primitives (MD5MAC). See https://github.com/psforever/PSCrypto for more information.
*/
try {
CryptoInterface.initialize()
logger.info("PSCrypto initialized")
}
catch {
case e : UnsatisfiedLinkError =>
logger.error(e)("Unable to initialize " + CryptoInterface.libName)
CryptoInterface.printEnvironment()
sys.exit(1)
}
// TODO: pluralize "processors"
logger.info(s"Detected ${Runtime.getRuntime.availableProcessors()} available logical processors")
logger.info("Starting actor subsystems...")
/** Make sure we capture Akka messages (but only INFO and above)
*
* This same config can be specified in a configuration file, but that's more work at this point.
* In the future we will have a unified configuration file specific to this server
*/
val config = Map(
"akka.loggers" -> List("akka.event.slf4j.Slf4jLogger").asJava,
"akka.loglevel" -> "INFO",
"akka.logging-filter" -> "akka.event.slf4j.Slf4jLoggingFilter"
).asJava
/** Start up the main actor system. This "system" is the home for all actors running on this server */
val system = ActorSystem("PsLogin", ConfigFactory.parseMap(config))
/** Create pipelines for the login and world servers
*
* The first node in the pipe is an Actor that handles the crypto for protecting packets.
* After any crypto operations have been applied or unapplied, the packets are passed on to the next
* actor in the chain. For an incoming packet, this is a player session handler. For an outgoing packet
* this is the session router, which returns the packet to the sending host.
*
* See SessionRouter.scala for a diagram
*/
val loginTemplate = List(
SessionPipeline("crypto-session-", Props[CryptoSessionActor]),
SessionPipeline("login-session-", Props[LoginSessionActor])
)
val worldTemplate = List(
SessionPipeline("crypto-session-", Props[CryptoSessionActor]),
SessionPipeline("world-session-", Props[WorldSessionActor])
)
val loginServerPort = 51000
val worldServerPort = 51001
/** Create two actors for handling the login and world server endpoints */
val listener = system.actorOf(Props(new UdpListener(Props(new SessionRouter(loginTemplate)), "login-session-router",
LoginConfig.serverIpAddress, loginServerPort)), "login-udp-endpoint")
val worldListener = system.actorOf(Props(new UdpListener(Props(new SessionRouter(worldTemplate)), "world-session-router",
LoginConfig.serverIpAddress, worldServerPort)), "world-udp-endpoint")
logger.info(s"NOTE: Set client.ini to point to ${LoginConfig.serverIpAddress.getHostAddress}:$loginServerPort")
// Add our shutdown hook (this works for Control+C as well, but not in Cygwin)
sys addShutdownHook {
// TODO: clean up active sessions and close resources safely
logger.info("Login server now shutting down...")
}
// Wait forever until the actor system shuts down
Await.result(system.whenTerminated, Duration.Inf)
}
}