mirror of
https://github.com/ProjectSWGCore/forwarder.git
synced 2026-01-15 22:04:29 -05:00
Changed forwarder to use websockets and improve stability/reusability of connection
This commit is contained in:
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,6 +1,3 @@
|
||||
[submodule "client-holocore"]
|
||||
path = client-holocore
|
||||
url = https://github.com/ProjectSWGCore/client-holocore.git
|
||||
[submodule "pswgcommon"]
|
||||
path = pswgcommon
|
||||
url = https://github.com/ProjectSWGCore/pswgcommon.git
|
||||
|
||||
@@ -20,17 +20,17 @@ idea {
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
maven("https://dev.joshlarson.me/maven2")
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java.outputDir = File(java.outputDir.toString().replace("\\${File.separatorChar}java", ""))
|
||||
|
||||
dependencies {
|
||||
implementation(group="org.jetbrains", name="annotations", version="20.1.0")
|
||||
implementation(project(":pswgcommon"))
|
||||
implementation(project(":client-holocore"))
|
||||
api(group="me.joshlarson", name="jlcommon-network", version="1.1.1")
|
||||
implementation(group="me.joshlarson", name="websocket", version="0.9.3")
|
||||
}
|
||||
}
|
||||
test {
|
||||
@@ -45,5 +45,5 @@ tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach
|
||||
kotlinOptions {
|
||||
jvmTarget = kotlinTargetJdk
|
||||
}
|
||||
destinationDir = sourceSets.main.get().java.outputDir
|
||||
destinationDirectory.set(File(destinationDirectory.get().asFile.path.replace("kotlin", "java")))
|
||||
}
|
||||
|
||||
Submodule client-holocore deleted from 42894ee7e1
@@ -1 +1 @@
|
||||
include(":pswgcommon", ":client-holocore")
|
||||
include(":pswgcommon")
|
||||
|
||||
@@ -4,15 +4,12 @@ import com.projectswg.forwarder.intents.*
|
||||
import me.joshlarson.jlcommon.concurrency.Delay
|
||||
import me.joshlarson.jlcommon.control.IntentManager
|
||||
import me.joshlarson.jlcommon.control.Manager
|
||||
import me.joshlarson.jlcommon.control.SafeMain
|
||||
import me.joshlarson.jlcommon.log.Log
|
||||
import me.joshlarson.jlcommon.log.Log.LogLevel
|
||||
import me.joshlarson.jlcommon.log.log_wrapper.ConsoleLogWrapper
|
||||
import me.joshlarson.jlcommon.utilities.ThreadUtilities
|
||||
import java.io.*
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.URI
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.nio.file.Files
|
||||
import java.util.*
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import java.util.zip.ZipEntry
|
||||
@@ -109,11 +106,11 @@ class Forwarder {
|
||||
|
||||
class ForwarderData internal constructor() {
|
||||
|
||||
var address: InetSocketAddress? = null
|
||||
var isVerifyServer = true
|
||||
var isEncryptionEnabled = true
|
||||
var baseConnectionUri: String? = null
|
||||
var username: String? = null
|
||||
var password: String? = null
|
||||
var protocolVersion: String? = null
|
||||
|
||||
var loginPort = 0
|
||||
var zonePort = 0
|
||||
var pingPort = 0
|
||||
@@ -121,22 +118,15 @@ class Forwarder {
|
||||
var outboundTunerInterval = 20
|
||||
var crashed: Boolean = false
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
val connectionUri: URI
|
||||
get() {
|
||||
val encodedUsername = Base64.getEncoder().encodeToString((username ?: "").encodeToByteArray())
|
||||
val encodedPassword = Base64.getEncoder().encodeToString((password ?: "").encodeToByteArray())
|
||||
val encodedProtocolVersion = Base64.getEncoder().encodeToString((protocolVersion ?: "").encodeToByteArray())
|
||||
val connectionUriStr = "$baseConnectionUri?username=$encodedUsername&password=$encodedPassword&protocolVersion=$encodedProtocolVersion"
|
||||
return URI(connectionUriStr)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
SafeMain.main("") { mainRunnable() }
|
||||
}
|
||||
|
||||
private fun mainRunnable() {
|
||||
Log.addWrapper(ConsoleLogWrapper(LogLevel.TRACE))
|
||||
val forwarder = Forwarder()
|
||||
forwarder.data.address = InetSocketAddress(44463)
|
||||
forwarder.run()
|
||||
ThreadUtilities.printActiveThreads()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,34 +2,13 @@ package com.projectswg.forwarder.resources.networking
|
||||
|
||||
import com.projectswg.common.network.NetBuffer
|
||||
import com.projectswg.common.network.packets.PacketType
|
||||
import com.projectswg.common.network.packets.swg.holo.login.HoloLoginRequestPacket
|
||||
import com.projectswg.common.network.packets.swg.login.LoginClientId
|
||||
import com.projectswg.common.network.packets.swg.login.LoginClusterStatus
|
||||
import com.projectswg.forwarder.Forwarder.ForwarderData
|
||||
import com.projectswg.holocore.client.HolocoreSocket
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
|
||||
class NetInterceptor(private val data: ForwarderData) {
|
||||
|
||||
fun interceptClient(holocore: HolocoreSocket, data: ByteArray) {
|
||||
if (data.size < 6) {
|
||||
holocore.send(data)
|
||||
return
|
||||
}
|
||||
val bb = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN)
|
||||
|
||||
when (PacketType.fromCrc(bb.getInt(2))) {
|
||||
PacketType.LOGIN_CLIENT_ID -> {
|
||||
val loginClientId = LoginClientId(NetBuffer.wrap(bb))
|
||||
if (loginClientId.username == this.data.username && loginClientId.password.isEmpty())
|
||||
loginClientId.password = this.data.password
|
||||
holocore.send(HoloLoginRequestPacket(loginClientId.username, loginClientId.password).encode().array())
|
||||
}
|
||||
else -> holocore.send(data)
|
||||
}
|
||||
}
|
||||
|
||||
fun interceptServer(data: ByteArray): ByteArray {
|
||||
if (data.size < 6)
|
||||
return data
|
||||
|
||||
@@ -0,0 +1,306 @@
|
||||
package com.projectswg.forwarder.resources.server
|
||||
|
||||
import com.projectswg.common.network.NetBuffer
|
||||
import com.projectswg.common.network.packets.PacketType
|
||||
import com.projectswg.common.network.packets.swg.holo.HoloConnectionStarted
|
||||
import com.projectswg.common.network.packets.swg.holo.HoloConnectionStopped
|
||||
import com.projectswg.common.network.packets.swg.holo.login.HoloLoginResponsePacket
|
||||
import com.projectswg.common.network.packets.swg.login.*
|
||||
import com.projectswg.forwarder.Forwarder
|
||||
import com.projectswg.forwarder.intents.DataPacketOutboundIntent
|
||||
import com.projectswg.forwarder.intents.ServerConnectedIntent
|
||||
import com.projectswg.forwarder.intents.ServerDisconnectedIntent
|
||||
import com.projectswg.forwarder.resources.networking.NetInterceptor
|
||||
import me.joshlarson.jlcommon.concurrency.Delay
|
||||
import me.joshlarson.jlcommon.control.IntentChain
|
||||
import me.joshlarson.jlcommon.control.IntentManager
|
||||
import me.joshlarson.jlcommon.log.Log
|
||||
import me.joshlarson.websocket.client.WebSocketClientCallback
|
||||
import me.joshlarson.websocket.client.WebSocketClientProtocol
|
||||
import me.joshlarson.websocket.common.WebSocketHandler
|
||||
import me.joshlarson.websocket.common.parser.http.HttpResponse
|
||||
import me.joshlarson.websocket.common.parser.websocket.WebSocketCloseReason
|
||||
import me.joshlarson.websocket.common.parser.websocket.WebsocketFrame
|
||||
import me.joshlarson.websocket.common.parser.websocket.WebsocketFrameType
|
||||
import java.io.Closeable
|
||||
import java.io.IOException
|
||||
import java.io.OutputStream
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.Socket
|
||||
import java.net.URI
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import java.security.SecureRandom
|
||||
import java.util.*
|
||||
import java.util.concurrent.Semaphore
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.SSLSocket
|
||||
import kotlin.concurrent.withLock
|
||||
|
||||
class HolocoreConnection(private val intentManager: IntentManager,
|
||||
private val interceptor: NetInterceptor,
|
||||
private val forwarderData: Forwarder.ForwarderData) : Closeable {
|
||||
|
||||
private val intentChain: IntentChain = IntentChain()
|
||||
private val upgradeDelaySemaphore = Semaphore(0)
|
||||
private val outputStreamLock = ReentrantLock()
|
||||
private val connectionStatus = AtomicReference(ServerConnectionStatus.DISCONNECTED)
|
||||
private val disconnectReason = AtomicReference(HoloConnectionStopped.ConnectionStoppedReason.UNKNOWN)
|
||||
private val connectionSender = AtomicReference<((ByteArray) -> Unit)?>(null)
|
||||
|
||||
private val initialConnectionUri = forwarderData.connectionUri
|
||||
private val socket: Socket = createSocket(initialConnectionUri)
|
||||
private val port: Int = when {
|
||||
initialConnectionUri.port != -1 -> initialConnectionUri.port
|
||||
initialConnectionUri.scheme == "wss" -> 443
|
||||
initialConnectionUri.scheme == "ws" -> 80
|
||||
else -> {
|
||||
Log.e("Undefined port in connection URI")
|
||||
throw IllegalArgumentException("initialConnectionUri")
|
||||
}
|
||||
}
|
||||
|
||||
private lateinit var connectionUri: URI
|
||||
private lateinit var outputStream: OutputStream
|
||||
private lateinit var wsProtocol: WebSocketClientProtocol
|
||||
|
||||
override fun close() {
|
||||
socket.close()
|
||||
}
|
||||
|
||||
fun getConnectionStatus(): ServerConnectionStatus {
|
||||
return connectionStatus.get()
|
||||
}
|
||||
|
||||
fun setDisconnectReason(reason: HoloConnectionStopped.ConnectionStoppedReason) {
|
||||
this.disconnectReason.set(reason)
|
||||
}
|
||||
|
||||
fun sendPacket(packet: ByteArray) {
|
||||
val bb = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN)
|
||||
|
||||
when (PacketType.fromCrc(bb.getInt(2))) {
|
||||
PacketType.LOGIN_CLIENT_ID -> {
|
||||
val loginClientId = LoginClientId(NetBuffer.wrap(bb))
|
||||
if (loginClientId.username == this.forwarderData.username && loginClientId.password.isEmpty())
|
||||
loginClientId.password = this.forwarderData.password
|
||||
|
||||
Log.i("Received login packet for %s", loginClientId.username)
|
||||
forwarderData.username = loginClientId.username
|
||||
forwarderData.password = loginClientId.password
|
||||
forwarderData.protocolVersion = PROTOCOL_VERSION
|
||||
upgradeDelaySemaphore.drainPermits()
|
||||
upgradeDelaySemaphore.release()
|
||||
connectionSender.get()?.invoke(loginClientId.encode().array())
|
||||
}
|
||||
else -> {
|
||||
connectionSender.get()?.invoke(packet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun handle() {
|
||||
if (!connectAndUpgrade())
|
||||
return
|
||||
|
||||
handleOnConnect()
|
||||
try {
|
||||
handleReadLoop()
|
||||
startGracefulDisconnect()
|
||||
} finally {
|
||||
handleOnDisconnect()
|
||||
}
|
||||
}
|
||||
|
||||
private fun connectAndUpgrade(): Boolean {
|
||||
Log.t("Attempting to connect to server at %s://%s:%d", initialConnectionUri.scheme, initialConnectionUri.host, port)
|
||||
try {
|
||||
socket.connect(InetSocketAddress(initialConnectionUri.host, port), CONNECT_TIMEOUT)
|
||||
} catch (e: IOException) {
|
||||
Log.w("Failed to connect to server: %s", e.message)
|
||||
return false
|
||||
} catch (e: InterruptedException) {
|
||||
Log.w("Server connection timed out")
|
||||
return false
|
||||
}
|
||||
intentChain.broadcastAfter(intentManager, ServerConnectedIntent())
|
||||
|
||||
Log.t("Connected to server via TCP - awaiting login packet...")
|
||||
upgradeDelaySemaphore.drainPermits() // Ensure we have exactly zero permits
|
||||
try {
|
||||
upgradeDelaySemaphore.tryAcquire(30, TimeUnit.SECONDS)
|
||||
} catch (e: InterruptedException) {
|
||||
Log.w("No login packet received within 30s - terminating connection")
|
||||
return false
|
||||
}
|
||||
|
||||
Log.t("Received login packet - upgrading to websocket")
|
||||
connectionUri = forwarderData.connectionUri
|
||||
outputStream = socket.getOutputStream()
|
||||
wsProtocol = WebSocketClientProtocol(WebsocketHandler(), connectionUri.rawPath + "?" + connectionUri.rawQuery, ::writeToOutputStream, socket::close)
|
||||
return true
|
||||
}
|
||||
|
||||
private fun handleOnConnect() {
|
||||
wsProtocol.onConnect()
|
||||
}
|
||||
|
||||
private fun handleReadLoop() {
|
||||
val inputStream = socket.getInputStream()
|
||||
val buffer = ByteArray(4096)
|
||||
val startOfConnection = System.nanoTime()
|
||||
|
||||
while (!Delay.isInterrupted()) {
|
||||
val n = inputStream.read(buffer)
|
||||
if (n <= 0)
|
||||
break
|
||||
|
||||
wsProtocol.onRead(buffer, 0, n)
|
||||
|
||||
if (connectionStatus.get() == ServerConnectionStatus.CONNECTING && System.nanoTime() - startOfConnection >= CONNECT_TIMEOUT) {
|
||||
Log.e("Failed to connect to server")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startGracefulDisconnect() {
|
||||
val inputStream = socket.getInputStream()
|
||||
val buffer = ByteArray(4096)
|
||||
|
||||
connectionStatus.set(ServerConnectionStatus.DISCONNECTING)
|
||||
wsProtocol.send(WebsocketFrame(WebsocketFrameType.BINARY, HoloConnectionStopped(disconnectReason.get()).encode().array()))
|
||||
wsProtocol.sendClose()
|
||||
|
||||
val startOfDisconnect = System.nanoTime()
|
||||
socket.soTimeout = 3000
|
||||
while (connectionStatus.get() != ServerConnectionStatus.DISCONNECTED && System.nanoTime() - startOfDisconnect < 5e9) {
|
||||
val n = inputStream.read(buffer)
|
||||
if (n <= 0)
|
||||
break
|
||||
|
||||
wsProtocol.onRead(buffer, 0, n)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handlePacket(data: ByteArray) {
|
||||
val bb = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN)
|
||||
when (PacketType.fromCrc(bb.getInt(2))) {
|
||||
PacketType.LOGIN_CLUSTER_STATUS -> {
|
||||
val cluster = LoginClusterStatus(NetBuffer.wrap(data))
|
||||
for (g in cluster.galaxies) {
|
||||
g.address = "127.0.0.1"
|
||||
g.zonePort = this.forwarderData.zonePort
|
||||
g.pingPort = this.forwarderData.pingPort
|
||||
}
|
||||
intentChain.broadcastAfter(intentManager, DataPacketOutboundIntent(cluster.encode().array()))
|
||||
}
|
||||
PacketType.HOLO_LOGIN_RESPONSE -> {
|
||||
val response = HoloLoginResponsePacket(NetBuffer.wrap(data))
|
||||
for (g in response.galaxies) {
|
||||
g.address = "127.0.0.1"
|
||||
g.zonePort = this.forwarderData.zonePort
|
||||
g.pingPort = this.forwarderData.pingPort
|
||||
}
|
||||
intentChain.broadcastAfter(intentManager, DataPacketOutboundIntent(LoginClientToken(ByteArray(24), 0, "").encode().array()))
|
||||
intentChain.broadcastAfter(intentManager, DataPacketOutboundIntent(CharacterCreationDisabled().encode().array()))
|
||||
intentChain.broadcastAfter(intentManager, DataPacketOutboundIntent(LoginEnumCluster(response.galaxies, 2).encode().array()))
|
||||
intentChain.broadcastAfter(intentManager, DataPacketOutboundIntent(EnumerateCharacterId(response.characters).encode().array()))
|
||||
intentChain.broadcastAfter(intentManager, DataPacketOutboundIntent(LoginClusterStatus(response.galaxies).encode().array()))
|
||||
}
|
||||
else -> {
|
||||
intentChain.broadcastAfter(intentManager, DataPacketOutboundIntent(interceptor.interceptServer(data)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleOnDisconnect() {
|
||||
wsProtocol.onDisconnect()
|
||||
|
||||
if (connectionStatus.get() != ServerConnectionStatus.DISCONNECTED) {
|
||||
Log.w("Server connection interrupted")
|
||||
} else {
|
||||
Log.i("Successfully closed server connection")
|
||||
}
|
||||
intentChain.broadcastAfter(intentManager, ServerDisconnectedIntent())
|
||||
}
|
||||
|
||||
private fun writeToOutputStream(data: ByteArray) {
|
||||
outputStreamLock.withLock {
|
||||
outputStream.write(data)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class WebsocketHandler : WebSocketClientCallback {
|
||||
override fun onUpgrade(obj: WebSocketHandler, response: HttpResponse) {
|
||||
connectionSender.set(obj::sendBinary)
|
||||
}
|
||||
|
||||
override fun onDisconnect(obj: WebSocketHandler, closeCode: Int, reason: String) {
|
||||
connectionStatus.set(ServerConnectionStatus.DISCONNECTED)
|
||||
}
|
||||
|
||||
override fun onBinaryMessage(obj: WebSocketHandler, rawData: ByteArray) {
|
||||
val dataBuffer = NetBuffer.wrap(rawData)
|
||||
dataBuffer.short
|
||||
when (dataBuffer.int) {
|
||||
HoloConnectionStarted.CRC -> {
|
||||
Log.i("Successfully connected to server at %s", connectionUri.toASCIIString())
|
||||
connectionStatus.set(ServerConnectionStatus.CONNECTED)
|
||||
}
|
||||
HoloConnectionStopped.CRC -> {
|
||||
val packet = HoloConnectionStopped()
|
||||
packet.decode(NetBuffer.wrap(rawData))
|
||||
if (connectionStatus.get() != ServerConnectionStatus.DISCONNECTING) {
|
||||
connectionStatus.set(ServerConnectionStatus.DISCONNECTING)
|
||||
obj.sendBinary(HoloConnectionStopped(packet.reason).encode().array())
|
||||
}
|
||||
obj.close(WebSocketCloseReason.NORMAL.statusCode.toInt(), packet.reason.name)
|
||||
}
|
||||
else -> {
|
||||
handlePacket(rawData)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class ServerConnectionStatus {
|
||||
CONNECTING, CONNECTED, DISCONNECTING, DISCONNECTED
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val PROTOCOL_VERSION = "20220620-15:00"
|
||||
private const val CONNECT_TIMEOUT = 5000
|
||||
|
||||
private fun createSocket(connectionUri: URI): Socket {
|
||||
if (connectionUri.scheme == "wss") {
|
||||
val sslContext = SSLContext.getInstance("TLSv1.3")
|
||||
val tm = null // To disable server verification, use: arrayOf<TrustManager>(TrustingTrustManager())
|
||||
sslContext.init(null, tm, SecureRandom())
|
||||
val sslSocket = sslContext.socketFactory.createSocket() as SSLSocket
|
||||
|
||||
sslSocket.enabledProtocols = arrayOf("TLSv1.3")
|
||||
// Last Updated: 02 May 2021
|
||||
sslSocket.enabledCipherSuites = sslSocket.supportedCipherSuites
|
||||
// We want either AES256 GCM or CHACHA20, in the TLSv1.3 cipher format
|
||||
.filter {it.startsWith("TLS_AES_256_GCM") || it.startsWith("TLS_CHACHA20")}
|
||||
// SHA256 and SHA384 are both solid hashing algorithms
|
||||
.filter {it.endsWith("SHA256") || it.endsWith("SHA384")}
|
||||
// Prioritize CHACHA20, because it is stream-based rather than block-based
|
||||
.sortedBy { if (it.startsWith("TLS_CHACHA20")) 0 else 1 }
|
||||
.toTypedArray()
|
||||
Log.t("Using TLSv1.3 ciphers: %s", Arrays.toString(sslSocket.enabledCipherSuites))
|
||||
|
||||
return sslSocket
|
||||
} else {
|
||||
return Socket()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -32,8 +32,8 @@ class ClientOutboundDataService : Service() {
|
||||
private val outboundBuffer: Array<SequencedOutbound?> = arrayOfNulls(4096)
|
||||
private val multiplexer: IntentMultiplexer = IntentMultiplexer(this, ProtocolStack::class.java, Packet::class.java)
|
||||
private val activeStacks: MutableSet<ProtocolStack> = ConcurrentHashMap.newKeySet()
|
||||
private val sendThread: BasicThread = BasicThread("outbound-sender", Runnable { this.persistentSend() })
|
||||
private val heartbeatThread: BasicScheduledThread = BasicScheduledThread("heartbeat", Runnable { this.heartbeat() })
|
||||
private val sendThread: BasicThread = BasicThread("outbound-sender") { this.persistentSend() }
|
||||
private val heartbeatThread: BasicScheduledThread = BasicScheduledThread("heartbeat") { this.heartbeat() }
|
||||
private val zoningIn: AtomicBoolean = AtomicBoolean(false)
|
||||
private val packetNotifyLock: ReentrantLock = ReentrantLock()
|
||||
private val packetNotify: Condition = packetNotifyLock.newCondition()
|
||||
|
||||
@@ -18,7 +18,6 @@ import java.nio.BufferUnderflowException
|
||||
import java.util.*
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import java.util.function.Consumer
|
||||
|
||||
class ClientServerService : Service() {
|
||||
|
||||
@@ -57,11 +56,11 @@ class ClientServerService : Service() {
|
||||
try {
|
||||
val data = sfi.data
|
||||
Log.t("Initializing login udp server...")
|
||||
loginServer = UDPServer(InetSocketAddress(InetAddress.getLoopbackAddress(), data.loginPort), 16384, Consumer<DatagramPacket> { this.onLoginPacket(it) })
|
||||
loginServer = UDPServer(InetSocketAddress(InetAddress.getLoopbackAddress(), data.loginPort), 16384) { this.onLoginPacket(it) }
|
||||
Log.t("Initializing zone udp server...")
|
||||
zoneServer = UDPServer(InetSocketAddress(InetAddress.getLoopbackAddress(), data.zonePort), 16384, Consumer<DatagramPacket> { this.onZonePacket(it) })
|
||||
zoneServer = UDPServer(InetSocketAddress(InetAddress.getLoopbackAddress(), data.zonePort), 16384) { this.onZonePacket(it) }
|
||||
Log.t("Initializing ping udp server...")
|
||||
pingServer = UDPServer(InetSocketAddress(InetAddress.getLoopbackAddress(), data.pingPort), 16384, Consumer<DatagramPacket> { this.onPingPacket(it) })
|
||||
pingServer = UDPServer(InetSocketAddress(InetAddress.getLoopbackAddress(), data.pingPort), 16384) { this.onPingPacket(it) }
|
||||
|
||||
Log.t("Binding to login server...")
|
||||
loginServer.bind { this.customizeUdpServer(it) }
|
||||
|
||||
@@ -56,7 +56,7 @@ class IntentRecordingService : Service() {
|
||||
if (data == null)
|
||||
log(cci, "")
|
||||
else
|
||||
log(cci, "Address='%s' Username='%s' Login='%d' Zone='%d'", data.address, data.username, data.loginPort, data.zonePort)
|
||||
log(cci, "Base URL='%s' Username='%s' Login='%d' Zone='%d'", data.baseConnectionUri, data.username, data.loginPort, data.zonePort)
|
||||
}
|
||||
|
||||
@IntentHandler
|
||||
@@ -75,7 +75,7 @@ class IntentRecordingService : Service() {
|
||||
if (data == null)
|
||||
log(sfi, "")
|
||||
else
|
||||
log(sfi, "Address='%s' Username='%s' Login='%d' Zone='%d'", data.address, data.username, data.loginPort, data.zonePort)
|
||||
log(sfi, "Base URL='%s' Username='%s' Login='%d' Zone='%d'", data.baseConnectionUri, data.username, data.loginPort, data.zonePort)
|
||||
}
|
||||
|
||||
@IntentHandler
|
||||
@@ -84,7 +84,7 @@ class IntentRecordingService : Service() {
|
||||
if (data == null)
|
||||
log(sci, "")
|
||||
else
|
||||
log(sci, "Address='%s' Username='%s' Login='%d' Zone='%d'", data.address, data.username, data.loginPort, data.zonePort)
|
||||
log(sci, "Base URL='%s' Username='%s' Login='%d' Zone='%d'", data.baseConnectionUri, data.username, data.loginPort, data.zonePort)
|
||||
}
|
||||
|
||||
@IntentHandler
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
module com.projectswg.forwarder {
|
||||
requires com.projectswg.common;
|
||||
requires com.projectswg.holocore.client;
|
||||
requires org.jetbrains.annotations;
|
||||
requires me.joshlarson.jlcommon;
|
||||
requires me.joshlarson.jlcommon.network;
|
||||
requires me.joshlarson.websocket;
|
||||
|
||||
requires java.management;
|
||||
requires kotlin.stdlib;
|
||||
|
||||
@@ -1,31 +1,24 @@
|
||||
package com.projectswg.forwarder.services.server
|
||||
|
||||
import com.projectswg.common.network.NetBuffer
|
||||
import com.projectswg.common.network.packets.PacketType
|
||||
import com.projectswg.common.network.packets.swg.holo.HoloConnectionStopped
|
||||
import com.projectswg.common.network.packets.swg.holo.login.HoloLoginResponsePacket
|
||||
import com.projectswg.common.network.packets.swg.login.*
|
||||
import com.projectswg.forwarder.Forwarder.ForwarderData
|
||||
import com.projectswg.forwarder.intents.*
|
||||
import com.projectswg.forwarder.resources.networking.NetInterceptor
|
||||
import com.projectswg.holocore.client.HolocoreSocket
|
||||
import com.projectswg.forwarder.resources.server.HolocoreConnection
|
||||
import me.joshlarson.jlcommon.concurrency.BasicThread
|
||||
import me.joshlarson.jlcommon.control.IntentChain
|
||||
import me.joshlarson.jlcommon.control.IntentHandler
|
||||
import me.joshlarson.jlcommon.control.Service
|
||||
import me.joshlarson.jlcommon.log.Log
|
||||
import java.io.IOException
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
class ServerConnectionService : Service() {
|
||||
|
||||
private val intentChain: IntentChain = IntentChain()
|
||||
private val thread: BasicThread = BasicThread("server-connection", Runnable { this.primaryConnectionLoop() })
|
||||
private val thread: BasicThread = BasicThread("server-connection") { this.primaryConnectionLoop() }
|
||||
|
||||
private val currentConnection = AtomicReference<HolocoreConnection?>(null)
|
||||
|
||||
private lateinit var interceptor: NetInterceptor // set by StartForwarderIntent
|
||||
private lateinit var data: ForwarderData // set by StartForwarderIntent
|
||||
private var holocore: HolocoreSocket? = null
|
||||
|
||||
override fun stop(): Boolean {
|
||||
return stopRunningLoop(HoloConnectionStopped.ConnectionStoppedReason.APPLICATION)
|
||||
@@ -44,8 +37,7 @@ class ServerConnectionService : Service() {
|
||||
|
||||
@IntentHandler
|
||||
private fun handleRequestServerConnectionIntent(rsci: RequestServerConnectionIntent) {
|
||||
val holocore = this.holocore
|
||||
if (holocore != null)
|
||||
if (currentConnection.get()?.getConnectionStatus() == HolocoreConnection.ServerConnectionStatus.CONNECTING)
|
||||
return // It's trying to connect - give it a little more time
|
||||
|
||||
if (stopRunningLoop(HoloConnectionStopped.ConnectionStoppedReason.NEW_CONNECTION))
|
||||
@@ -59,114 +51,35 @@ class ServerConnectionService : Service() {
|
||||
|
||||
@IntentHandler
|
||||
private fun handleDataPacketInboundIntent(dpii: DataPacketInboundIntent) {
|
||||
val holocore = this.holocore ?: return
|
||||
val data = dpii.data
|
||||
if (data.size < 6) {
|
||||
return // not a valid packet
|
||||
}
|
||||
val bb = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN)
|
||||
|
||||
when (PacketType.fromCrc(bb.getInt(2))) {
|
||||
PacketType.LOGIN_CLIENT_ID -> {
|
||||
val loginClientId = LoginClientId(NetBuffer.wrap(bb))
|
||||
if (loginClientId.username == this.data.username && loginClientId.password.isEmpty())
|
||||
loginClientId.password = this.data.password
|
||||
holocore.send(loginClientId.encode().array())
|
||||
}
|
||||
else -> holocore.send(data)
|
||||
}
|
||||
currentConnection.get()?.sendPacket(data)
|
||||
}
|
||||
|
||||
private fun stopRunningLoop(reason: HoloConnectionStopped.ConnectionStoppedReason): Boolean {
|
||||
if (!thread.isExecuting)
|
||||
return true
|
||||
thread.stop(true)
|
||||
|
||||
currentConnection.get()?.setDisconnectReason(reason)
|
||||
Log.d("Terminating connection with the server. Reason: $reason")
|
||||
holocore?.send(HoloConnectionStopped(reason).encode().array())
|
||||
holocore?.close()
|
||||
return thread.awaitTermination(1000)
|
||||
thread.stop(true)
|
||||
return thread.awaitTermination(5000)
|
||||
}
|
||||
|
||||
private fun primaryConnectionLoop() {
|
||||
var didConnect = false
|
||||
try {
|
||||
val address = data.address ?: return
|
||||
HolocoreSocket(address.address, address.port, data.isVerifyServer, data.isEncryptionEnabled).use { holocore ->
|
||||
this.holocore = holocore
|
||||
Log.t("Attempting to connect to server at %s", holocore.remoteAddress)
|
||||
try {
|
||||
holocore.connect(CONNECT_TIMEOUT)
|
||||
} catch (e: IOException) {
|
||||
Log.e("Failed to connect to server")
|
||||
return
|
||||
}
|
||||
didConnect = true
|
||||
intentChain.broadcastAfter(intentManager, ServerConnectedIntent())
|
||||
Log.i("Successfully connected to server at %s", holocore.remoteAddress)
|
||||
|
||||
while (holocore.isConnected) {
|
||||
if (primaryConnectionLoopReceive(holocore))
|
||||
continue // More packets to receive
|
||||
|
||||
if (holocore.isConnected)
|
||||
Log.w("Server connection interrupted")
|
||||
else
|
||||
Log.w("Server closed connection!")
|
||||
return
|
||||
}
|
||||
HolocoreConnection(intentManager, interceptor, data).use {
|
||||
currentConnection.set(it)
|
||||
it.handle()
|
||||
currentConnection.set(null)
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
Log.w("Caught unknown exception in server connection! %s: %s", t.javaClass.name, t.message)
|
||||
Log.w(t)
|
||||
} finally {
|
||||
Log.i("Disconnected from server.")
|
||||
if (didConnect)
|
||||
intentChain.broadcastAfter(intentManager, ServerDisconnectedIntent())
|
||||
this.holocore = null
|
||||
}
|
||||
}
|
||||
|
||||
private fun primaryConnectionLoopReceive(holocore: HolocoreSocket): Boolean {
|
||||
val inbound = holocore.receive() ?: return false
|
||||
val data = inbound.data
|
||||
|
||||
if (data.size < 6)
|
||||
return true
|
||||
val bb = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN)
|
||||
when (PacketType.fromCrc(bb.getInt(2))) {
|
||||
PacketType.LOGIN_CLUSTER_STATUS -> {
|
||||
val cluster = LoginClusterStatus(NetBuffer.wrap(data))
|
||||
for (g in cluster.galaxies) {
|
||||
g.address = "127.0.0.1"
|
||||
g.zonePort = this.data.zonePort
|
||||
g.pingPort = this.data.pingPort
|
||||
}
|
||||
intentChain.broadcastAfter(intentManager, DataPacketOutboundIntent(cluster.encode().array()))
|
||||
}
|
||||
PacketType.HOLO_LOGIN_RESPONSE -> {
|
||||
val response = HoloLoginResponsePacket(NetBuffer.wrap(data))
|
||||
for (g in response.galaxies) {
|
||||
g.address = "127.0.0.1"
|
||||
g.zonePort = this.data.zonePort
|
||||
g.pingPort = this.data.pingPort
|
||||
}
|
||||
intentChain.broadcastAfter(intentManager, DataPacketOutboundIntent(LoginClientToken(ByteArray(24), 0, "").encode().array()))
|
||||
intentChain.broadcastAfter(intentManager, DataPacketOutboundIntent(CharacterCreationDisabled().encode().array()))
|
||||
intentChain.broadcastAfter(intentManager, DataPacketOutboundIntent(LoginEnumCluster(response.galaxies, 2).encode().array()))
|
||||
intentChain.broadcastAfter(intentManager, DataPacketOutboundIntent(EnumerateCharacterId(response.characters).encode().array()))
|
||||
intentChain.broadcastAfter(intentManager, DataPacketOutboundIntent(LoginClusterStatus(response.galaxies).encode().array()))
|
||||
}
|
||||
else -> {
|
||||
intentChain.broadcastAfter(intentManager, DataPacketOutboundIntent(interceptor.interceptServer(inbound.data)))
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val CONNECT_TIMEOUT = 5000
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user