mirror of
https://github.com/ProjectSWGCore/Holocore.git
synced 2026-09-11 21:45:26 -04:00
Changed network protocol to websockets
This commit is contained in:
@@ -33,12 +33,14 @@ sourceSets {
|
||||
dependencies {
|
||||
implementation(project(":pswgcommon"))
|
||||
implementation(kotlin("stdlib"))
|
||||
implementation(kotlin("reflect"))
|
||||
|
||||
implementation(group="org.xerial", name="sqlite-jdbc", version="3.30.1")
|
||||
implementation(group="org.mongodb", name="mongodb-driver-sync", version="3.12.2")
|
||||
implementation(group="me.joshlarson", name="fast-json", version="3.0.1")
|
||||
implementation(group="me.joshlarson", name="jlcommon-network", version="1.1.0")
|
||||
implementation(group="me.joshlarson", name="jlcommon-argparse", version="0.9.5")
|
||||
implementation(group="me.joshlarson", name="websocket", version="0.9.3")
|
||||
implementation(group="com.github.madsboddum", name="swgterrain", version="1.1.3")
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
Submodule pswgcommon updated: 3fb3cf6473...0b12cc3713
+34
@@ -0,0 +1,34 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (c) 2022 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
|
||||
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
|
||||
* Our goal is to create an emulator which will provide a server for players to *
|
||||
* continue playing a game similar to the one they used to play. We are basing *
|
||||
* it on the final publish of the game prior to end-game events. *
|
||||
* *
|
||||
* This file is part of Holocore. *
|
||||
* *
|
||||
* --------------------------------------------------------------------------------*
|
||||
* *
|
||||
* Holocore is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as *
|
||||
* published by the Free Software Foundation, either version 3 of the *
|
||||
* License, or (at your option) any later version. *
|
||||
* *
|
||||
* Holocore 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 Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
|
||||
***********************************************************************************/
|
||||
|
||||
package com.projectswg.holocore.intents.support.global.login
|
||||
|
||||
import com.projectswg.holocore.resources.support.global.player.Player
|
||||
import me.joshlarson.jlcommon.control.Intent
|
||||
import java.net.SocketAddress
|
||||
|
||||
class RequestLoginIntent(val player: Player, val username: String, val password: String, val clientVersion: String, val socketAddress: SocketAddress) : Intent()
|
||||
+98
-33
@@ -28,6 +28,7 @@ package com.projectswg.holocore.resources.support.global.network
|
||||
|
||||
import com.projectswg.common.network.NetBuffer
|
||||
import com.projectswg.common.network.NetworkProtocol
|
||||
import com.projectswg.common.network.packets.PacketType
|
||||
import com.projectswg.common.network.packets.SWGPacket
|
||||
import com.projectswg.common.network.packets.swg.ErrorMessage
|
||||
import com.projectswg.common.network.packets.swg.admin.AdminPacket
|
||||
@@ -35,6 +36,8 @@ 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.HoloConnectionStopped.ConnectionStoppedReason
|
||||
import com.projectswg.common.network.packets.swg.holo.HoloSetProtocolVersion
|
||||
import com.projectswg.common.network.packets.swg.zone.object_controller.ObjectController
|
||||
import com.projectswg.holocore.intents.support.global.login.RequestLoginIntent
|
||||
import com.projectswg.holocore.intents.support.global.network.ConnectionClosedIntent
|
||||
import com.projectswg.holocore.intents.support.global.network.ConnectionOpenedIntent
|
||||
import com.projectswg.holocore.intents.support.global.network.InboundPacketIntent
|
||||
@@ -42,63 +45,91 @@ import com.projectswg.holocore.resources.support.data.server_info.StandardLog
|
||||
import com.projectswg.holocore.resources.support.global.player.AccessLevel
|
||||
import com.projectswg.holocore.resources.support.global.player.Player
|
||||
import me.joshlarson.jlcommon.control.IntentChain
|
||||
import me.joshlarson.jlcommon.log.Log
|
||||
import me.joshlarson.websocket.common.WebSocketHandler
|
||||
import me.joshlarson.websocket.common.parser.http.HttpRequest
|
||||
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 me.joshlarson.websocket.server.WebSocketServerCallback
|
||||
import me.joshlarson.websocket.server.WebSocketServerProtocol
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.SocketAddress
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.*
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import java.util.function.Consumer
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.concurrent.withLock
|
||||
|
||||
class NetworkClient(private val remoteAddress: SocketAddress, private val write: (ByteBuffer) -> Unit, private val closeChannel: () -> Unit): TCPServerChannel {
|
||||
class NetworkClient(private val remoteAddress: SocketAddress, write: (ByteBuffer) -> Unit, closeChannel: () -> Unit): TCPServerChannel, WebSocketServerCallback {
|
||||
|
||||
private val inboundBuffer: NetBuffer
|
||||
private val intentChain: IntentChain
|
||||
private val connected: AtomicBoolean
|
||||
private val status: AtomicReference<SessionStatus>
|
||||
val player: Player
|
||||
private val inboundBuffer = ByteBuffer.allocate(INBOUND_BUFFER_SIZE)
|
||||
private val intentChain = IntentChain()
|
||||
private val connected = AtomicBoolean(true)
|
||||
private val status = AtomicReference(SessionStatus.DISCONNECTED)
|
||||
private val wsProtocol = WebSocketServerProtocol(this, { data -> write(ByteBuffer.wrap(data)) }, closeChannel)
|
||||
private val writeLock = ReentrantLock()
|
||||
|
||||
val player = Player(SESSION_ID.getAndIncrement(), remoteAddress as InetSocketAddress?) { this.addToOutbound(it) }
|
||||
|
||||
val id: Long
|
||||
get() = player.networkId
|
||||
|
||||
private var clientDisconnectReason: ConnectionStoppedReason
|
||||
private var serverDisconnectReason: ConnectionStoppedReason
|
||||
|
||||
init {
|
||||
this.inboundBuffer = NetBuffer.allocate(INBOUND_BUFFER_SIZE)
|
||||
|
||||
this.intentChain = IntentChain()
|
||||
this.connected = AtomicBoolean(true)
|
||||
this.status = AtomicReference(SessionStatus.DISCONNECTED)
|
||||
this.player = Player(SESSION_ID.getAndIncrement(), remoteAddress as InetSocketAddress?, Consumer<SWGPacket> { this.addToOutbound(it) })
|
||||
this.clientDisconnectReason = ConnectionStoppedReason.UNKNOWN
|
||||
this.serverDisconnectReason = ConnectionStoppedReason.UNKNOWN
|
||||
}
|
||||
private var clientDisconnectReason = ConnectionStoppedReason.UNKNOWN
|
||||
private var serverDisconnectReason = ConnectionStoppedReason.UNKNOWN
|
||||
|
||||
@JvmOverloads
|
||||
fun close(reason: ConnectionStoppedReason = ConnectionStoppedReason.OTHER_SIDE_TERMINATED) {
|
||||
if (connected.getAndSet(false)) {
|
||||
serverDisconnectReason = reason
|
||||
write(NetworkProtocol.encode(HoloConnectionStopped(reason)).buffer)
|
||||
closeChannel()
|
||||
wsProtocol.sendClose(WebSocketCloseReason.NORMAL.statusCode.toInt(), reason.name)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getChannelBuffer(): ByteBuffer {
|
||||
return inboundBuffer.buffer
|
||||
return inboundBuffer
|
||||
}
|
||||
|
||||
override fun onRead() {
|
||||
inboundBuffer.flip()
|
||||
while (true) {
|
||||
val p = NetworkProtocol.decode(inboundBuffer) ?: break
|
||||
if (!allowInbound(p))
|
||||
continue
|
||||
p.socketAddress = remoteAddress
|
||||
processPacket(p)
|
||||
intentChain.broadcastAfter(InboundPacketIntent(player, p))
|
||||
wsProtocol.onRead(inboundBuffer.array(), 0, inboundBuffer.position())
|
||||
inboundBuffer.position(0)
|
||||
}
|
||||
|
||||
override fun onUpgrade(obj: WebSocketHandler, request: HttpRequest) {
|
||||
val urlParameters = request.urlParameters
|
||||
val username = getUrlParameter(urlParameters, "username", true) ?: return
|
||||
val password = getUrlParameter(urlParameters, "password", true) ?: return
|
||||
val protocolVersion = getUrlParameter(urlParameters, "protocolVersion", true) ?: return
|
||||
StandardLog.onPlayerTrace(this, player, "requested login for $username and protocol version $protocolVersion")
|
||||
|
||||
if (protocolVersion == NetworkProtocol.VERSION) {
|
||||
onConnected()
|
||||
} else {
|
||||
close(ConnectionStoppedReason.INVALID_PROTOCOL)
|
||||
return
|
||||
}
|
||||
|
||||
RequestLoginIntent(player, username, password, "20051010-17:00", remoteAddress).broadcast()
|
||||
}
|
||||
|
||||
override fun onBinaryMessage(obj: WebSocketHandler, data: ByteArray) {
|
||||
if (data.size < 6)
|
||||
return
|
||||
val swg = NetBuffer.wrap(data)
|
||||
|
||||
swg.position(2)
|
||||
val crc: Int = swg.int
|
||||
swg.position(0)
|
||||
|
||||
if (crc == ObjectController.CRC) {
|
||||
onInbound(ObjectController.decodeController(swg))
|
||||
} else {
|
||||
val packet = PacketType.getForCrc(crc)
|
||||
packet?.decode(swg)
|
||||
onInbound(packet)
|
||||
}
|
||||
inboundBuffer.compact()
|
||||
}
|
||||
|
||||
override fun onOpened() {
|
||||
@@ -116,9 +147,43 @@ class NetworkClient(private val remoteAddress: SocketAddress, private val write:
|
||||
return "NetworkClient[$remoteAddress]"
|
||||
}
|
||||
|
||||
private fun getUrlParameter(urlParameters: Map<String, List<String>>, key: String, required: Boolean): String? {
|
||||
if (!urlParameters.containsKey(key)) {
|
||||
if (required) {
|
||||
StandardLog.onPlayerError(this, player, "onUpgrade: no $key specified - disconnecting")
|
||||
close(ConnectionStoppedReason.APPLICATION)
|
||||
}
|
||||
return null
|
||||
}
|
||||
val encodedValues = urlParameters[key]
|
||||
if (encodedValues?.size != 1) {
|
||||
if (required) {
|
||||
StandardLog.onPlayerError(this, player, "onUpgrade: invalid count of $key: ${encodedValues?.size ?: -1}")
|
||||
close(ConnectionStoppedReason.APPLICATION)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
return String(Base64.getDecoder().decode(encodedValues[0]))
|
||||
}
|
||||
|
||||
private fun onInbound(p: SWGPacket?) {
|
||||
if (p == null || !allowInbound(p))
|
||||
return
|
||||
p.socketAddress = remoteAddress
|
||||
processPacket(p)
|
||||
intentChain.broadcastAfter(InboundPacketIntent(player, p))
|
||||
}
|
||||
|
||||
private fun addToOutbound(p: SWGPacket) {
|
||||
if (allowOutbound(p) && connected.get()) {
|
||||
write(NetworkProtocol.encode(p).buffer)
|
||||
val encoded = p.encode()
|
||||
if (encoded.position() != encoded.capacity())
|
||||
Log.w("SWGPacket %s has invalid array length. Expected: %d Actual: %d", p, encoded.remaining(), encoded.capacity())
|
||||
|
||||
writeLock.withLock {
|
||||
wsProtocol.send(WebsocketFrame(WebsocketFrameType.BINARY, encoded.buffer.array()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+23
-17
@@ -42,6 +42,7 @@ import com.projectswg.common.network.packets.swg.zone.ServerNowEpochTime;
|
||||
import com.projectswg.holocore.ProjectSWG;
|
||||
import com.projectswg.holocore.intents.support.global.login.LoginEventIntent;
|
||||
import com.projectswg.holocore.intents.support.global.login.LoginEventIntent.LoginEvent;
|
||||
import com.projectswg.holocore.intents.support.global.login.RequestLoginIntent;
|
||||
import com.projectswg.holocore.intents.support.global.network.CloseConnectionIntent;
|
||||
import com.projectswg.holocore.intents.support.global.network.InboundPacketIntent;
|
||||
import com.projectswg.holocore.intents.support.global.zone.creation.DeleteCharacterIntent;
|
||||
@@ -61,6 +62,7 @@ import com.projectswg.holocore.services.support.objects.ObjectStorageService.Obj
|
||||
import me.joshlarson.jlcommon.control.IntentHandler;
|
||||
import me.joshlarson.jlcommon.control.Service;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
@@ -82,9 +84,8 @@ public class LoginService extends Service {
|
||||
@IntentHandler
|
||||
private void handleObjectCreatedIntent(ObjectCreatedIntent oci) {
|
||||
SWGObject obj = oci.getObject();
|
||||
if (!(obj instanceof PlayerObject))
|
||||
if (!(obj instanceof PlayerObject player))
|
||||
return;
|
||||
PlayerObject player = (PlayerObject) obj;
|
||||
CreatureObject creature = (CreatureObject) player.getParent();
|
||||
if (creature == null)
|
||||
return;
|
||||
@@ -107,8 +108,8 @@ public class LoginService extends Service {
|
||||
SWGPacket p = gpi.getPacket();
|
||||
if (p instanceof HoloLoginRequestPacket) {
|
||||
handleLogin(gpi.getPlayer(), (HoloLoginRequestPacket) p);
|
||||
} else if (p instanceof LoginClientId) {
|
||||
handleLogin(gpi.getPlayer(), (LoginClientId) p);
|
||||
} else if (p instanceof LoginClientId id) {
|
||||
handleLogin(gpi.getPlayer(), id.getUsername(), id.getPassword(), id.getVersion(), id.getSocketAddress());
|
||||
} else if (p instanceof DeleteCharacterRequest) {
|
||||
handleCharDeletion(gpi.getPlayer(), (DeleteCharacterRequest) p);
|
||||
} else if (p instanceof LagRequest) {
|
||||
@@ -118,6 +119,11 @@ public class LoginService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
@IntentHandler
|
||||
private void handleRequestLoginIntent(RequestLoginIntent rli) {
|
||||
handleLogin(rli.getPlayer(), rli.getUsername(), rli.getPassword(), rli.getClientVersion(), rli.getSocketAddress());
|
||||
}
|
||||
|
||||
private String getServerString() {
|
||||
String name = PswgDatabase.INSTANCE.getConfig().getString(this, "loginServerName", "LoginServer");
|
||||
int id = PswgDatabase.INSTANCE.getConfig().getInt(this, "loginServerId", 1);
|
||||
@@ -183,7 +189,7 @@ public class LoginService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleLogin(Player player, LoginClientId id) {
|
||||
private void handleLogin(Player player, String username, String password, String clientVersion, SocketAddress socketAddress) {
|
||||
if (player.getPlayerState() == PlayerState.LOGGED_IN) { // Client occasionally sends multiple login requests
|
||||
sendLoginSuccessPacket(player);
|
||||
return;
|
||||
@@ -193,37 +199,37 @@ public class LoginService extends Service {
|
||||
player.setPlayerState(PlayerState.LOGGING_IN);
|
||||
player.setPlayerServer(PlayerServer.LOGIN);
|
||||
final boolean doClientCheck = PswgDatabase.INSTANCE.getConfig().getBoolean(this, "loginVersionChecks", true);
|
||||
if (!id.getVersion().equals(REQUIRED_VERSION) && doClientCheck) {
|
||||
StandardLog.onPlayerEvent(this, player, "failed to login [incorrect version: %s] from %s", id.getVersion(), id.getSocketAddress());
|
||||
onLoginClientVersionError(player, id);
|
||||
if (!clientVersion.equals(REQUIRED_VERSION) && doClientCheck) {
|
||||
StandardLog.onPlayerEvent(this, player, "failed to login [incorrect version: %s] from %s", clientVersion, socketAddress);
|
||||
onLoginClientVersionError(player, clientVersion);
|
||||
return;
|
||||
}
|
||||
|
||||
UserMetadata user = PswgDatabase.INSTANCE.getUsers().getUser(id.getUsername());
|
||||
player.setUsername(id.getUsername());
|
||||
UserMetadata user = PswgDatabase.INSTANCE.getUsers().getUser(username);
|
||||
player.setUsername(username);
|
||||
if (user == null) {
|
||||
StandardLog.onPlayerEvent(this, player, "failed to login [incorrect username] from %s", id.getSocketAddress());
|
||||
StandardLog.onPlayerEvent(this, player, "failed to login [incorrect username] from %s", socketAddress);
|
||||
onInvalidUserPass(player);
|
||||
player.sendPacket(new ErrorMessage("Login Failed!", "Incorrect username", false));
|
||||
player.sendPacket(new LoginIncorrectClientId(getServerString(), REQUIRED_VERSION));
|
||||
} else if (user.isBanned()) {
|
||||
StandardLog.onPlayerEvent(this, player, "failed to login [banned] from %s", id.getSocketAddress());
|
||||
StandardLog.onPlayerEvent(this, player, "failed to login [banned] from %s", socketAddress);
|
||||
onLoginBanned(player);
|
||||
player.sendPacket(new ErrorMessage("Login Failed!", "Sorry, you're banned!", false));
|
||||
} else if (isUserValid(user, id.getPassword())) {
|
||||
StandardLog.onPlayerEvent(this, player, "logged in from %s", id.getSocketAddress());
|
||||
} else if (isUserValid(user, password)) {
|
||||
StandardLog.onPlayerEvent(this, player, "logged in from %s", socketAddress);
|
||||
onSuccessfulLogin(user, player);
|
||||
sendLoginSuccessPacket(player);
|
||||
} else {
|
||||
StandardLog.onPlayerEvent(this, player, "failed to login [incorrect password] from %s", id.getSocketAddress());
|
||||
StandardLog.onPlayerEvent(this, player, "failed to login [incorrect password] from %s", socketAddress);
|
||||
onInvalidUserPass(player);
|
||||
player.sendPacket(new ErrorMessage("Login Failed!", "Incorrect password", false));
|
||||
player.sendPacket(new LoginIncorrectClientId(getServerString(), REQUIRED_VERSION));
|
||||
}
|
||||
}
|
||||
|
||||
private void onLoginClientVersionError(Player player, LoginClientId id) {
|
||||
player.sendPacket(new ErrorMessage("Login Failed!", "Invalid Client Version Code: " + id.getVersion(), false));
|
||||
private void onLoginClientVersionError(Player player, String clientVersion) {
|
||||
player.sendPacket(new ErrorMessage("Login Failed!", "Invalid Client Version Code: " + clientVersion, false));
|
||||
player.setPlayerState(PlayerState.DISCONNECTED);
|
||||
new LoginEventIntent(player.getNetworkId(), LoginEvent.LOGIN_FAIL_INVALID_VERSION_CODE).broadcast();
|
||||
}
|
||||
|
||||
@@ -10,10 +10,12 @@ open module holocore {
|
||||
requires me.joshlarson.jlcommon;
|
||||
requires me.joshlarson.jlcommon.network;
|
||||
requires me.joshlarson.jlcommon.argparse;
|
||||
requires me.joshlarson.websocket;
|
||||
|
||||
requires com.projectswg.common;
|
||||
requires fast.json;
|
||||
requires kotlin.stdlib;
|
||||
requires kotlin.reflect;
|
||||
|
||||
requires swgterrain;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user