mirror of
https://gitlab.bixilon.de/bixilon/minosoft.git
synced 2025-09-10 07:48:29 -04:00
packet sender, receiver, listen for packets, network fixes and improvements
This commit is contained in:
parent
b388253a49
commit
25f439499d
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2020-2023 Moritz Zwerger
|
||||
* Copyright (C) 2020-2024 Moritz Zwerger
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
@ -84,7 +84,7 @@ object ConnectionTestUtil {
|
||||
WORLD.forceSet(connection, createWorld(connection, light, (worldSize * 2 + 1).pow(2)))
|
||||
PLAYER.forceSet(connection, LocalPlayerEntity(connection.account, connection, signature))
|
||||
connection.player.startInit()
|
||||
NETWORK.forceSet(connection, TestNetwork())
|
||||
NETWORK.forceSet(connection, TestNetwork(connection))
|
||||
EVENTS.forceSet(connection, EventMaster())
|
||||
PROFILES.forceSet(connection, profiles)
|
||||
ASSETS_MANAGER.forceSet(connection, ConnectionAssetsManager(AssetsManagerProperties(PackProperties(version.packFormat))))
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2020-2023 Moritz Zwerger
|
||||
* Copyright (C) 2020-2024 Moritz Zwerger
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
@ -14,7 +14,9 @@
|
||||
package de.bixilon.minosoft.protocol.network.connection.play
|
||||
|
||||
import de.bixilon.kutil.cast.CastUtil.unsafeCast
|
||||
import de.bixilon.kutil.exception.Broken
|
||||
import de.bixilon.kutil.unsafe.UnsafeUtil.setUnsafeAccessible
|
||||
import de.bixilon.minosoft.protocol.network.network.client.ClientNetwork
|
||||
import de.bixilon.minosoft.protocol.network.network.client.test.TestNetwork
|
||||
import de.bixilon.minosoft.protocol.packets.c2s.C2SPacket
|
||||
|
||||
@ -24,8 +26,9 @@ object PacketTestUtil {
|
||||
return network.unsafeCast()
|
||||
}
|
||||
|
||||
fun PlayConnection.assertPacket(expected: C2SPacket) {
|
||||
val found = test().take() ?: throw AssertionError("Expected packet $expected, but found [null]!")
|
||||
fun ClientNetwork.assertPacket(expected: C2SPacket) {
|
||||
if (this !is TestNetwork) Broken("Not testing")
|
||||
val found = take() ?: throw AssertionError("Expected packet $expected, but found [null]!")
|
||||
if (found::class.java != expected::class.java) {
|
||||
throw AssertionError("Packet class expected: $expected, but found $found")
|
||||
}
|
||||
@ -39,25 +42,39 @@ object PacketTestUtil {
|
||||
}
|
||||
}
|
||||
|
||||
fun PlayConnection.assertNoPacket() {
|
||||
val packet = test().take()
|
||||
fun ClientNetwork.assertNoPacket() {
|
||||
if (this !is TestNetwork) Broken("Not testing")
|
||||
val packet = take()
|
||||
if (packet != null) {
|
||||
throw AssertionError("Expected no packet, but found $packet")
|
||||
}
|
||||
}
|
||||
|
||||
fun PlayConnection.assertPacket(expected: C2SPacket) {
|
||||
test().assertPacket(expected)
|
||||
}
|
||||
|
||||
fun PlayConnection.assertNoPacket() {
|
||||
test().assertNoPacket()
|
||||
}
|
||||
|
||||
@Deprecated("use assertPacket and assertNoPacket")
|
||||
fun PlayConnection.assertOnlyPacket(packet: C2SPacket) {
|
||||
assertPacket(packet)
|
||||
assertNoPacket()
|
||||
}
|
||||
|
||||
fun <T : C2SPacket> PlayConnection.assertPacket(type: Class<T>): T {
|
||||
val packet = test().take() ?: throw AssertionError("Expected packet of type $type, but found [null]!")
|
||||
fun <T : C2SPacket> ClientNetwork.assertPacket(type: Class<T>): T {
|
||||
if (this !is TestNetwork) Broken("Not testing")
|
||||
val packet = take() ?: throw AssertionError("Expected packet of type $type, but found [null]!")
|
||||
val clazz = packet::class.java
|
||||
if (type.isAssignableFrom(clazz)) {
|
||||
return packet.unsafeCast()
|
||||
}
|
||||
throw AssertionError("Expected packet of type $type, but found found $packet!")
|
||||
}
|
||||
|
||||
fun <T : C2SPacket> PlayConnection.assertPacket(type: Class<T>): T {
|
||||
return test().assertPacket(type)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2020-2024 Moritz Zwerger
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
||||
*/
|
||||
|
||||
package de.bixilon.minosoft.protocol.network.network.client.netty.packet.receiver
|
||||
|
||||
import de.bixilon.minosoft.protocol.network.connection.Connection
|
||||
import de.bixilon.minosoft.protocol.network.network.client.netty.exceptions.PacketHandleException
|
||||
import de.bixilon.minosoft.protocol.network.network.client.test.TestNetwork
|
||||
import de.bixilon.minosoft.protocol.packets.registry.PacketType
|
||||
import de.bixilon.minosoft.protocol.packets.s2c.S2CPacket
|
||||
import org.testng.Assert.assertEquals
|
||||
import org.testng.Assert.assertTrue
|
||||
import org.testng.annotations.Test
|
||||
|
||||
@Test(groups = ["network"])
|
||||
class PacketReceiverTest {
|
||||
val type = PacketType("test", false, false, null, null)
|
||||
|
||||
|
||||
private fun create(): PacketReceiver {
|
||||
val network = TestNetwork()
|
||||
return network.receiver
|
||||
}
|
||||
|
||||
fun `simple receiving`() {
|
||||
val receiver = create()
|
||||
val packet = TestS2CP()
|
||||
receiver.onReceive(type, packet)
|
||||
assertEquals(packet.handled, 1)
|
||||
}
|
||||
|
||||
fun `error handling`() {
|
||||
val receiver = create()
|
||||
val packet = TestS2CP(false)
|
||||
try {
|
||||
receiver.onReceive(type, packet)
|
||||
throw AssertionError("Not thrown")
|
||||
} catch (error: PacketHandleException) {
|
||||
}
|
||||
assertEquals(packet.handled, 1)
|
||||
}
|
||||
|
||||
fun `listen and keep`() {
|
||||
val receiver = create()
|
||||
var packets = 0
|
||||
receiver.listen { assertTrue(it is TestS2CP); packets++; false }
|
||||
val packet = TestS2CP(true)
|
||||
receiver.onReceive(type, packet)
|
||||
assertEquals(packets, 1)
|
||||
assertEquals(packet.handled, 1)
|
||||
}
|
||||
|
||||
fun `listen and discard`() {
|
||||
val receiver = create()
|
||||
var packets = 0
|
||||
receiver.listen { assertTrue(it is TestS2CP); packets++; true }
|
||||
val packet = TestS2CP(true)
|
||||
receiver.onReceive(type, packet)
|
||||
assertEquals(packets, 1)
|
||||
assertEquals(packet.handled, 0)
|
||||
}
|
||||
|
||||
// TODO: async handling
|
||||
|
||||
|
||||
private class TestS2CP(
|
||||
val handle: Boolean = true,
|
||||
) : S2CPacket {
|
||||
var handled = 0
|
||||
|
||||
override fun log(reducedLog: Boolean) = Unit
|
||||
|
||||
|
||||
override fun handle(connection: Connection) {
|
||||
handled++
|
||||
if (!handle) throw Exception("Testing...")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2020-2024 Moritz Zwerger
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
||||
*/
|
||||
|
||||
package de.bixilon.minosoft.protocol.network.network.client.netty.packet.sender
|
||||
|
||||
import de.bixilon.minosoft.protocol.network.connection.play.PacketTestUtil.assertNoPacket
|
||||
import de.bixilon.minosoft.protocol.network.connection.play.PacketTestUtil.assertPacket
|
||||
import de.bixilon.minosoft.protocol.network.network.client.test.TestNetwork
|
||||
import de.bixilon.minosoft.protocol.packets.c2s.C2SPacket
|
||||
import org.testng.Assert.assertEquals
|
||||
import org.testng.Assert.assertTrue
|
||||
import org.testng.annotations.Test
|
||||
|
||||
@Test(groups = ["network"])
|
||||
class PacketSenderTest {
|
||||
|
||||
|
||||
private fun create(): PacketSender {
|
||||
val network = TestNetwork()
|
||||
return network.sender
|
||||
}
|
||||
|
||||
fun `send normal`() {
|
||||
val sender = create()
|
||||
sender.send(DummyC2SPacket())
|
||||
sender.network.assertPacket(DummyC2SPacket::class.java)
|
||||
sender.network.assertNoPacket()
|
||||
}
|
||||
|
||||
fun `sending paused`() {
|
||||
val sender = create()
|
||||
sender.paused = true
|
||||
sender.send(DummyC2SPacket())
|
||||
sender.network.assertNoPacket()
|
||||
sender.paused = false
|
||||
sender.network.assertPacket(DummyC2SPacket::class.java)
|
||||
sender.network.assertNoPacket()
|
||||
}
|
||||
|
||||
fun `packet listener not discarding`() {
|
||||
val sender = create()
|
||||
var packets = 0
|
||||
sender.listen { assertTrue(it is DummyC2SPacket); packets++; false }
|
||||
sender.send(DummyC2SPacket())
|
||||
sender.network.assertPacket(DummyC2SPacket::class.java)
|
||||
sender.network.assertNoPacket()
|
||||
assertEquals(packets, 1)
|
||||
}
|
||||
|
||||
fun `packet listener discarding`() {
|
||||
val sender = create()
|
||||
var packets = 0
|
||||
sender.listen { assertTrue(it is DummyC2SPacket); packets++; true }
|
||||
sender.send(DummyC2SPacket())
|
||||
sender.network.assertNoPacket()
|
||||
assertEquals(packets, 1)
|
||||
}
|
||||
|
||||
|
||||
private class DummyC2SPacket : C2SPacket {
|
||||
override fun log(reducedLog: Boolean) = Unit
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2020-2023 Moritz Zwerger
|
||||
* Copyright (C) 2020-2024 Moritz Zwerger
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
@ -14,18 +14,27 @@
|
||||
package de.bixilon.minosoft.protocol.network.network.client.test
|
||||
|
||||
import de.bixilon.minosoft.protocol.address.ServerAddress
|
||||
import de.bixilon.minosoft.protocol.network.connection.Connection
|
||||
import de.bixilon.minosoft.protocol.network.connection.play.ConnectionTestUtil.createConnection
|
||||
import de.bixilon.minosoft.protocol.network.network.client.ClientNetwork
|
||||
import de.bixilon.minosoft.protocol.network.network.client.netty.exceptions.PacketHandleException
|
||||
import de.bixilon.minosoft.protocol.network.network.client.netty.packet.receiver.PacketReceiver
|
||||
import de.bixilon.minosoft.protocol.network.network.client.netty.packet.sender.PacketSender
|
||||
import de.bixilon.minosoft.protocol.packets.c2s.C2SPacket
|
||||
import de.bixilon.minosoft.protocol.protocol.ProtocolStates
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
import javax.crypto.Cipher
|
||||
|
||||
class TestNetwork : ClientNetwork {
|
||||
override var connected: Boolean = false
|
||||
class TestNetwork(
|
||||
connection: Connection = createConnection(),
|
||||
) : ClientNetwork {
|
||||
override val sender = PacketSender(this)
|
||||
override val receiver = PacketReceiver(this, connection)
|
||||
override var connected: Boolean = true
|
||||
override var state: ProtocolStates = ProtocolStates.PLAY
|
||||
override var compressionThreshold: Int = -1
|
||||
override var encrypted: Boolean = false
|
||||
override var receive = true
|
||||
override val detached = false
|
||||
|
||||
private var queue = ConcurrentLinkedQueue<C2SPacket>()
|
||||
|
||||
@ -47,9 +56,8 @@ class TestNetwork : ClientNetwork {
|
||||
|
||||
override fun detach() = Unit
|
||||
|
||||
override fun pauseSending(pause: Boolean) = Unit
|
||||
|
||||
override fun send(packet: C2SPacket) {
|
||||
override fun forceSend(packet: C2SPacket) {
|
||||
if (queue.size > 15) {
|
||||
// leaking
|
||||
return
|
||||
@ -63,4 +71,8 @@ class TestNetwork : ClientNetwork {
|
||||
}
|
||||
return queue.remove()
|
||||
}
|
||||
|
||||
override fun handleError(error: Throwable) {
|
||||
throw PacketHandleException(error)
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2020-2023 Moritz Zwerger
|
||||
* Copyright (C) 2020-2024 Moritz Zwerger
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
@ -40,12 +40,7 @@ class RenderLoop(
|
||||
init {
|
||||
var paused = false
|
||||
context::state.observe(this) {
|
||||
paused = if (paused) {
|
||||
// context.queue.clear() // TODO: That might cause bugs and memory leaks
|
||||
false
|
||||
} else {
|
||||
it == RenderingStates.PAUSED
|
||||
}
|
||||
paused = if (paused) false else it == RenderingStates.PAUSED
|
||||
}
|
||||
context.profile.performance::slowRendering.observe(this) { this.slowRendering = it }
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2020-2023 Moritz Zwerger
|
||||
* Copyright (C) 2020-2024 Moritz Zwerger
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
@ -47,7 +47,7 @@ object DebugKeyBindings {
|
||||
ignoreConsumer = true,
|
||||
)) {
|
||||
connection.util.sendDebugMessage("Pausing incoming packets: ${it.format()}")
|
||||
connection.network.receive = it
|
||||
connection.network.receiver.paused = it
|
||||
}
|
||||
|
||||
register(PAUSE_OUTGOING, KeyBinding(
|
||||
@ -56,7 +56,7 @@ object DebugKeyBindings {
|
||||
ignoreConsumer = true,
|
||||
)) {
|
||||
connection.util.sendDebugMessage("Pausing outgoing packets: ${it.format()}")
|
||||
connection.network.pauseSending(it)
|
||||
connection.network.sender.paused = it
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2020-2023 Moritz Zwerger
|
||||
* Copyright (C) 2020-2024 Moritz Zwerger
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
@ -14,6 +14,8 @@
|
||||
package de.bixilon.minosoft.protocol.network.network.client
|
||||
|
||||
import de.bixilon.minosoft.protocol.address.ServerAddress
|
||||
import de.bixilon.minosoft.protocol.network.network.client.netty.packet.receiver.PacketReceiver
|
||||
import de.bixilon.minosoft.protocol.network.network.client.netty.packet.sender.PacketSender
|
||||
import de.bixilon.minosoft.protocol.packets.c2s.C2SPacket
|
||||
import de.bixilon.minosoft.protocol.protocol.ProtocolStates
|
||||
import javax.crypto.Cipher
|
||||
@ -21,11 +23,14 @@ import javax.crypto.Cipher
|
||||
interface ClientNetwork {
|
||||
val connected: Boolean
|
||||
val encrypted: Boolean
|
||||
val detached: Boolean
|
||||
|
||||
var state: ProtocolStates
|
||||
var receive: Boolean
|
||||
val compressionThreshold: Int
|
||||
|
||||
val receiver: PacketReceiver
|
||||
val sender: PacketSender
|
||||
|
||||
fun connect(address: ServerAddress, native: Boolean)
|
||||
fun disconnect()
|
||||
fun detach()
|
||||
@ -33,7 +38,8 @@ interface ClientNetwork {
|
||||
fun setupEncryption(encrypt: Cipher, decrypt: Cipher)
|
||||
fun setupCompression(threshold: Int)
|
||||
|
||||
fun pauseSending(pause: Boolean)
|
||||
fun send(packet: C2SPacket) = sender.send(packet)
|
||||
fun forceSend(packet: C2SPacket)
|
||||
|
||||
fun send(packet: C2SPacket)
|
||||
fun handleError(error: Throwable)
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2020-2023 Moritz Zwerger
|
||||
* Copyright (C) 2020-2024 Moritz Zwerger
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
@ -14,7 +14,6 @@
|
||||
package de.bixilon.minosoft.protocol.network.network.client.netty
|
||||
|
||||
import de.bixilon.kutil.cast.CastUtil.nullCast
|
||||
import de.bixilon.kutil.concurrent.pool.DefaultThreadPool
|
||||
import de.bixilon.kutil.observer.DataObserver.Companion.observed
|
||||
import de.bixilon.minosoft.config.profile.profiles.other.OtherProfileManager
|
||||
import de.bixilon.minosoft.protocol.address.ServerAddress
|
||||
@ -27,6 +26,8 @@ import de.bixilon.minosoft.protocol.network.network.client.netty.exceptions.Pack
|
||||
import de.bixilon.minosoft.protocol.network.network.client.netty.exceptions.ciritical.CriticalNetworkException
|
||||
import de.bixilon.minosoft.protocol.network.network.client.netty.natives.NioNatives
|
||||
import de.bixilon.minosoft.protocol.network.network.client.netty.natives.TransportNatives
|
||||
import de.bixilon.minosoft.protocol.network.network.client.netty.packet.receiver.PacketReceiver
|
||||
import de.bixilon.minosoft.protocol.network.network.client.netty.packet.sender.PacketSender
|
||||
import de.bixilon.minosoft.protocol.network.network.client.netty.pipeline.compression.PacketDeflater
|
||||
import de.bixilon.minosoft.protocol.network.network.client.netty.pipeline.compression.PacketInflater
|
||||
import de.bixilon.minosoft.protocol.network.network.client.netty.pipeline.encryption.PacketDecryptor
|
||||
@ -50,6 +51,8 @@ import javax.crypto.Cipher
|
||||
class NettyClient(
|
||||
val connection: Connection,
|
||||
) : SimpleChannelInboundHandler<Any>(), ClientNetwork {
|
||||
override val sender = PacketSender(this)
|
||||
override val receiver = PacketReceiver(this, connection)
|
||||
private var address: ServerAddress? = null
|
||||
override var connected by observed(false)
|
||||
private set
|
||||
@ -58,14 +61,8 @@ class NettyClient(
|
||||
override var encrypted: Boolean = false
|
||||
private set
|
||||
private var channel: Channel? = null
|
||||
private val packetQueue: MutableList<C2SPacket> = mutableListOf() // Used for pause sending
|
||||
private var sendingPaused = false
|
||||
private var detached = false
|
||||
override var receive = true
|
||||
set(value) {
|
||||
channel?.config()?.isAutoRead = value
|
||||
field = value
|
||||
}
|
||||
override var detached = false
|
||||
private set
|
||||
|
||||
override fun connect(address: ServerAddress, native: Boolean) {
|
||||
this.address = address
|
||||
@ -133,24 +130,7 @@ class NettyClient(
|
||||
channel?.close()
|
||||
}
|
||||
|
||||
override fun pauseSending(pause: Boolean) {
|
||||
this.sendingPaused = pause
|
||||
if (!sendingPaused) {
|
||||
DefaultThreadPool += {
|
||||
for (packet in packetQueue) {
|
||||
send(packet)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun send(packet: C2SPacket) {
|
||||
// TODO: packet listener
|
||||
if (detached) return
|
||||
if (sendingPaused) {
|
||||
packetQueue += packet
|
||||
return
|
||||
}
|
||||
override fun forceSend(packet: C2SPacket) {
|
||||
val channel = getChannel() ?: return
|
||||
|
||||
packet.log((connection.nullCast<PlayConnection>()?.profiles?.other ?: OtherProfileManager.selected).log.reducedProtocolLog)
|
||||
@ -166,7 +146,7 @@ class NettyClient(
|
||||
context.channel().config().setOption(ChannelOption.TCP_NODELAY, true)
|
||||
} catch (_: Throwable) {
|
||||
}
|
||||
context.channel().config().isAutoRead = this.receive
|
||||
context.channel().config().isAutoRead = true
|
||||
this.channel = context.channel()
|
||||
connected = true
|
||||
}
|
||||
@ -177,7 +157,7 @@ class NettyClient(
|
||||
connected = false
|
||||
}
|
||||
|
||||
fun handleError(error: Throwable) {
|
||||
override fun handleError(error: Throwable) {
|
||||
var cause = error
|
||||
if (cause is DecoderException) {
|
||||
cause = error.cause ?: cause
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2020-2023 Moritz Zwerger
|
||||
* Copyright (C) 2020-2024 Moritz Zwerger
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
@ -37,7 +37,7 @@ class NetworkPipeline(private val client: NettyClient) : ChannelInitializer<Sock
|
||||
|
||||
pipeline.addLast(LengthDecoder.NAME, LengthDecoder(maxLength))
|
||||
pipeline.addLast(PacketDecoder.NAME, PacketDecoder(client))
|
||||
pipeline.addLast(ClientPacketHandler.NAME, ClientPacketHandler(client))
|
||||
pipeline.addLast(ClientPacketHandler.NAME, ClientPacketHandler(client.receiver))
|
||||
|
||||
pipeline.addLast(LengthEncoder.NAME, LengthEncoder(maxLength))
|
||||
pipeline.addLast(PacketEncoder.NAME, PacketEncoder(client))
|
||||
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2020-2024 Moritz Zwerger
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
||||
*/
|
||||
|
||||
package de.bixilon.minosoft.protocol.network.network.client.netty.packet.receiver
|
||||
|
||||
import de.bixilon.minosoft.protocol.packets.s2c.S2CPacket
|
||||
|
||||
fun interface C2SPacketListener {
|
||||
|
||||
/**
|
||||
* Called for every packet that is received from the server before it is handled.
|
||||
* Depending on the time it takes to call all onReceive methods, serious network latency can be introduced.
|
||||
* @return true, if the packet should be instantly discarded and not handled, false if not.
|
||||
*/
|
||||
fun onReceive(packet: S2CPacket): Boolean
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2020-2024 Moritz Zwerger
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
||||
*/
|
||||
|
||||
package de.bixilon.minosoft.protocol.network.network.client.netty.packet.receiver
|
||||
|
||||
import de.bixilon.kutil.concurrent.pool.DefaultThreadPool
|
||||
import de.bixilon.kutil.concurrent.pool.ThreadPool
|
||||
import de.bixilon.kutil.concurrent.pool.runnable.ForcePooledRunnable
|
||||
import de.bixilon.kutil.exception.ExceptionUtil.ignoreAll
|
||||
import de.bixilon.minosoft.config.profile.profiles.other.OtherProfileManager
|
||||
import de.bixilon.minosoft.protocol.network.connection.Connection
|
||||
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
|
||||
import de.bixilon.minosoft.protocol.network.network.client.ClientNetwork
|
||||
import de.bixilon.minosoft.protocol.network.network.client.netty.exceptions.NetworkException
|
||||
import de.bixilon.minosoft.protocol.network.network.client.netty.exceptions.PacketHandleException
|
||||
import de.bixilon.minosoft.protocol.packets.registry.PacketType
|
||||
import de.bixilon.minosoft.protocol.packets.s2c.S2CPacket
|
||||
|
||||
class PacketReceiver(
|
||||
val network: ClientNetwork,
|
||||
val connection: Connection,
|
||||
) {
|
||||
private val listener: MutableList<C2SPacketListener> = ArrayList()
|
||||
private val queue: MutableList<QueuedS2CP<*>> = ArrayList(0)
|
||||
var paused = false
|
||||
set(value) {
|
||||
if (field == value) return
|
||||
if (!value) process() // try to keep order
|
||||
field = value
|
||||
if (!value) process() // flush again, so packets that might got queued won't get lost
|
||||
}
|
||||
|
||||
|
||||
private fun handle(packet: S2CPacket) {
|
||||
val profile = if (connection is PlayConnection) connection.profiles.other else OtherProfileManager.selected
|
||||
val reduced = profile.log.reducedProtocolLog
|
||||
packet.log(reduced)
|
||||
packet.handle(connection)
|
||||
}
|
||||
|
||||
private fun handleError(type: PacketType, error: Throwable) {
|
||||
if (type.extra != null) {
|
||||
type.extra.onError(error, connection)
|
||||
}
|
||||
network.handleError(error)
|
||||
}
|
||||
|
||||
private fun tryHandle(type: PacketType, packet: S2CPacket) {
|
||||
if (!network.connected) return
|
||||
|
||||
try {
|
||||
handle(packet)
|
||||
} catch (exception: NetworkException) {
|
||||
handleError(type, exception)
|
||||
} catch (error: Throwable) {
|
||||
handleError(type, PacketHandleException(error))
|
||||
}
|
||||
}
|
||||
|
||||
private fun PacketType.handleAsync(): Boolean {
|
||||
if (!threadSafe) return false
|
||||
if (lowPriority) return true
|
||||
if (DefaultThreadPool.queueSize >= (DefaultThreadPool.threadCount - 1)) return false // might backlog a bit
|
||||
return true
|
||||
}
|
||||
|
||||
private fun tryHandle2(type: PacketType, packet: S2CPacket) {
|
||||
if (type.handleAsync()) {
|
||||
DefaultThreadPool += ForcePooledRunnable(priority = if (type.lowPriority) ThreadPool.NORMAL else ThreadPool.HIGH) { tryHandle(type, packet) }
|
||||
} else {
|
||||
tryHandle(type, packet)
|
||||
}
|
||||
}
|
||||
|
||||
private fun notify(packet: S2CPacket): Boolean {
|
||||
if (listener.isEmpty()) return false
|
||||
for (listener in listener) {
|
||||
val discard = ignoreAll { listener.onReceive(packet) } ?: continue
|
||||
if (discard) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun onReceive(type: PacketType, packet: S2CPacket) {
|
||||
if (network.detached) return
|
||||
if (!network.connected) return
|
||||
val discard = notify(packet)
|
||||
if (discard) return
|
||||
|
||||
if (paused) {
|
||||
queue += QueuedS2CP(type, packet)
|
||||
} else {
|
||||
tryHandle2(type, packet)
|
||||
}
|
||||
}
|
||||
|
||||
private fun process() {
|
||||
while (queue.isNotEmpty()) {
|
||||
val (type, packet) = queue.removeFirst()
|
||||
tryHandle(type, packet)
|
||||
}
|
||||
}
|
||||
|
||||
operator fun plusAssign(listener: C2SPacketListener) = listen(listener)
|
||||
|
||||
fun listen(listener: C2SPacketListener) {
|
||||
this.listener += listener
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2020-2023 Moritz Zwerger
|
||||
* Copyright (C) 2020-2024 Moritz Zwerger
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
@ -11,7 +11,7 @@
|
||||
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
||||
*/
|
||||
|
||||
package de.bixilon.minosoft.protocol.network.network.client.netty.pipeline
|
||||
package de.bixilon.minosoft.protocol.network.network.client.netty.packet.receiver
|
||||
|
||||
import de.bixilon.minosoft.protocol.packets.registry.PacketType
|
||||
import de.bixilon.minosoft.protocol.packets.s2c.S2CPacket
|
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2020-2024 Moritz Zwerger
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
||||
*/
|
||||
|
||||
package de.bixilon.minosoft.protocol.network.network.client.netty.packet.sender
|
||||
|
||||
import de.bixilon.kutil.exception.ExceptionUtil.ignoreAll
|
||||
import de.bixilon.minosoft.protocol.network.network.client.ClientNetwork
|
||||
import de.bixilon.minosoft.protocol.packets.c2s.C2SPacket
|
||||
|
||||
class PacketSender(
|
||||
val network: ClientNetwork,
|
||||
) {
|
||||
private val listener: MutableList<S2CPacketListener> = ArrayList()
|
||||
private val queue: MutableList<C2SPacket> = ArrayList(0)
|
||||
var paused = false
|
||||
set(value) {
|
||||
if (field == value) return
|
||||
if (!value) flush() // try to keep order
|
||||
field = value
|
||||
if (!value) flush() // flush again, so packets that might got queued won't get lost
|
||||
}
|
||||
|
||||
private fun notify(packet: C2SPacket): Boolean {
|
||||
if (listener.isEmpty()) return false
|
||||
for (listener in listener) {
|
||||
val discard = ignoreAll { listener.onSend(packet) } ?: continue
|
||||
if (discard) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun send(packet: C2SPacket) {
|
||||
if (network.detached) return
|
||||
if (!network.connected) return
|
||||
val discard = notify(packet)
|
||||
if (discard) return
|
||||
|
||||
if (paused) {
|
||||
queue += packet
|
||||
} else {
|
||||
network.forceSend(packet)
|
||||
}
|
||||
}
|
||||
|
||||
private fun flush() {
|
||||
while (queue.isNotEmpty()) {
|
||||
val packet = queue.removeFirst()
|
||||
network.forceSend(packet)
|
||||
}
|
||||
}
|
||||
|
||||
operator fun plusAssign(listener: S2CPacketListener) = listen(listener)
|
||||
|
||||
fun listen(listener: S2CPacketListener) {
|
||||
this.listener += listener
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2020-2024 Moritz Zwerger
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
||||
*/
|
||||
|
||||
package de.bixilon.minosoft.protocol.network.network.client.netty.packet.sender
|
||||
|
||||
import de.bixilon.minosoft.protocol.packets.c2s.C2SPacket
|
||||
|
||||
fun interface S2CPacketListener {
|
||||
|
||||
/**
|
||||
* Called for every packet that is sent to the server from the client.
|
||||
* Might be called from any thread
|
||||
* Depending on the time it takes to call all onSend methods, serious network latency can be introduced.
|
||||
* @return true, if the packet should be instantly discarded, false if not.
|
||||
*/
|
||||
fun onSend(packet: C2SPacket): Boolean
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2020-2023 Moritz Zwerger
|
||||
* Copyright (C) 2020-2024 Moritz Zwerger
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
@ -13,80 +13,17 @@
|
||||
|
||||
package de.bixilon.minosoft.protocol.network.network.client.netty.pipeline
|
||||
|
||||
import de.bixilon.kutil.cast.CastUtil.nullCast
|
||||
import de.bixilon.kutil.concurrent.pool.DefaultThreadPool
|
||||
import de.bixilon.kutil.concurrent.pool.ThreadPool
|
||||
import de.bixilon.kutil.concurrent.pool.runnable.ForcePooledRunnable
|
||||
import de.bixilon.minosoft.config.profile.profiles.other.OtherProfileManager
|
||||
import de.bixilon.minosoft.protocol.network.connection.Connection
|
||||
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
|
||||
import de.bixilon.minosoft.protocol.network.connection.status.StatusConnection
|
||||
import de.bixilon.minosoft.protocol.network.network.client.netty.NettyClient
|
||||
import de.bixilon.minosoft.protocol.network.network.client.netty.exceptions.NetworkException
|
||||
import de.bixilon.minosoft.protocol.network.network.client.netty.exceptions.PacketHandleException
|
||||
import de.bixilon.minosoft.protocol.network.network.client.netty.exceptions.WrongConnectionException
|
||||
import de.bixilon.minosoft.protocol.packets.registry.PacketType
|
||||
import de.bixilon.minosoft.protocol.packets.s2c.PlayS2CPacket
|
||||
import de.bixilon.minosoft.protocol.packets.s2c.S2CPacket
|
||||
import de.bixilon.minosoft.protocol.packets.s2c.StatusS2CPacket
|
||||
import de.bixilon.minosoft.protocol.network.network.client.netty.packet.receiver.PacketReceiver
|
||||
import de.bixilon.minosoft.protocol.network.network.client.netty.packet.receiver.QueuedS2CP
|
||||
import io.netty.channel.ChannelHandlerContext
|
||||
import io.netty.channel.SimpleChannelInboundHandler
|
||||
|
||||
class ClientPacketHandler(
|
||||
private val client: NettyClient,
|
||||
private val receiver: PacketReceiver,
|
||||
) : SimpleChannelInboundHandler<QueuedS2CP<*>>() {
|
||||
private val connection: Connection = client.connection
|
||||
|
||||
override fun channelRead0(context: ChannelHandlerContext, queued: QueuedS2CP<*>) {
|
||||
if (queued.type.threadSafe && (queued.type.lowPriority || DefaultThreadPool.queueSize < DefaultThreadPool.threadCount - 1)) { // only handle async when thread pool not busy
|
||||
val runnable = ForcePooledRunnable(priority = if (queued.type.lowPriority) ThreadPool.Priorities.NORMAL else ThreadPool.Priorities.HIGH) { tryHandle(context, queued.type, queued.packet) }
|
||||
DefaultThreadPool += runnable
|
||||
} else {
|
||||
tryHandle(context, queued.type, queued.packet)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleError(context: ChannelHandlerContext, type: PacketType, error: Throwable) {
|
||||
if (type.extra != null) {
|
||||
type.extra.onError(error, connection)
|
||||
}
|
||||
client.handleError(error)
|
||||
}
|
||||
|
||||
private fun tryHandle(context: ChannelHandlerContext, type: PacketType, packet: S2CPacket) {
|
||||
if (!client.connected) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
handle(packet)
|
||||
} catch (exception: NetworkException) {
|
||||
handleError(context, type, exception)
|
||||
} catch (error: Throwable) {
|
||||
handleError(context, type, PacketHandleException(error))
|
||||
}
|
||||
}
|
||||
|
||||
private fun handle(packet: S2CPacket) {
|
||||
// TODO: packet listener
|
||||
when (packet) {
|
||||
is PlayS2CPacket -> handle(packet)
|
||||
is StatusS2CPacket -> handle(packet)
|
||||
else -> throw IllegalStateException("Unknown packet type!")
|
||||
}
|
||||
}
|
||||
|
||||
private fun handle(packet: PlayS2CPacket) {
|
||||
val connection = connection.nullCast<PlayConnection>() ?: throw WrongConnectionException(PlayConnection::class.java, this.connection::class.java)
|
||||
packet.log(connection.profiles.other.log.reducedProtocolLog)
|
||||
packet.check(connection)
|
||||
packet.handle(connection)
|
||||
}
|
||||
|
||||
private fun handle(packet: StatusS2CPacket) {
|
||||
val connection = connection.nullCast<StatusConnection>() ?: throw WrongConnectionException(StatusConnection::class.java, this.connection::class.java)
|
||||
packet.log(OtherProfileManager.selected.log.reducedProtocolLog)
|
||||
packet.check(connection)
|
||||
packet.handle(connection)
|
||||
receiver.onReceive(queued.type, queued.packet)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2020-2023 Moritz Zwerger
|
||||
* Copyright (C) 2020-2024 Moritz Zwerger
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
@ -19,7 +19,7 @@ import de.bixilon.minosoft.protocol.network.network.client.netty.exceptions.Netw
|
||||
import de.bixilon.minosoft.protocol.network.network.client.netty.exceptions.PacketReadException
|
||||
import de.bixilon.minosoft.protocol.network.network.client.netty.exceptions.ciritical.UnknownPacketIdException
|
||||
import de.bixilon.minosoft.protocol.network.network.client.netty.exceptions.implementation.PacketNotImplementedException
|
||||
import de.bixilon.minosoft.protocol.network.network.client.netty.pipeline.QueuedS2CP
|
||||
import de.bixilon.minosoft.protocol.network.network.client.netty.packet.receiver.QueuedS2CP
|
||||
import de.bixilon.minosoft.protocol.packets.s2c.S2CPacket
|
||||
import de.bixilon.minosoft.protocol.protocol.DefaultPacketMapping
|
||||
import de.bixilon.minosoft.protocol.protocol.buffers.InByteBuffer
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2020-2023 Moritz Zwerger
|
||||
* Copyright (C) 2020-2024 Moritz Zwerger
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
@ -25,11 +25,9 @@ import de.bixilon.minosoft.util.logging.LogMessageType
|
||||
class ReadyS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket {
|
||||
|
||||
override fun handle(connection: PlayConnection) {
|
||||
connection.network.receive = false
|
||||
connection.util.prepareSpawn()
|
||||
connection.sendPacket(ReadyC2SP())
|
||||
connection.network.send(ReadyC2SP())
|
||||
connection.network.state = ProtocolStates.PLAY
|
||||
connection.network.receive = true
|
||||
}
|
||||
|
||||
override fun log(reducedLog: Boolean) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2020-2023 Moritz Zwerger
|
||||
* Copyright (C) 2020-2024 Moritz Zwerger
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
@ -34,10 +34,8 @@ class SuccessS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket {
|
||||
|
||||
override fun handle(connection: PlayConnection) {
|
||||
if (connection.version.hasConfigurationState) {
|
||||
connection.network.receive = false
|
||||
connection.sendPacket(ConfigureC2SP())
|
||||
connection.network.send(ConfigureC2SP())
|
||||
connection.network.state = ProtocolStates.CONFIGURATION
|
||||
connection.network.receive = true
|
||||
connection.sendBrand()
|
||||
connection.settingsManager.sendClientSettings()
|
||||
} else {
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2020-2023 Moritz Zwerger
|
||||
* Copyright (C) 2020-2024 Moritz Zwerger
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
@ -26,10 +26,8 @@ class ReconfigureS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket {
|
||||
|
||||
override fun handle(connection: PlayConnection) {
|
||||
connection.util.resetWorld()
|
||||
connection.network.receive = false
|
||||
connection.sendPacket(ReconfigureC2SP())
|
||||
connection.network.send(ReconfigureC2SP())
|
||||
connection.network.state = ProtocolStates.CONFIGURATION
|
||||
connection.network.receive = true
|
||||
}
|
||||
|
||||
override fun log(reducedLog: Boolean) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user