diff --git a/example/network-engines/ktor-network/build.gradle.kts b/example/network-engines/ktor-network/build.gradle.kts new file mode 100644 index 0000000..571df6c --- /dev/null +++ b/example/network-engines/ktor-network/build.gradle.kts @@ -0,0 +1,19 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +kotlin { + explicitApi() + withSourcesJar() + + linuxX64() + linuxArm64() + macosArm64() + mingwX64() + jvm { compilerOptions.jvmTarget = JvmTarget.JVM_1_8 } + + sourceSets { + jvmMain.dependencies { + api(project(":protocol")) + api(libs.ktor.network) + } + } +} \ No newline at end of file diff --git a/example/network-engines/ktor-network/src/commonMain/kotlin/engines/KtorNetworkSocketEngine.kt b/example/network-engines/ktor-network/src/commonMain/kotlin/engines/KtorNetworkSocketEngine.kt new file mode 100644 index 0000000..b1aeb28 --- /dev/null +++ b/example/network-engines/ktor-network/src/commonMain/kotlin/engines/KtorNetworkSocketEngine.kt @@ -0,0 +1,10 @@ +/* + * Copyright © 2026 RTAkland + * Author: RTAkland + * Date: 2026/9/8 + */ + + +package engines + + diff --git a/example/network-engines/netty/build.gradle.kts b/example/network-engines/netty/build.gradle.kts new file mode 100644 index 0000000..3193ef1 --- /dev/null +++ b/example/network-engines/netty/build.gradle.kts @@ -0,0 +1,16 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +kotlin { + explicitApi() + withSourcesJar() + + jvm { compilerOptions.jvmTarget = JvmTarget.JVM_1_8 } + + sourceSets { + jvmMain.dependencies { + api(project(":protocol")) + api(libs.netty.handler) + api(libs.netty.buffer) + } + } +} \ No newline at end of file diff --git a/example/network-engines/netty/src/jvmMain/kotlin/engines/NettyNetworkSocketEngine.kt b/example/network-engines/netty/src/jvmMain/kotlin/engines/NettyNetworkSocketEngine.kt new file mode 100644 index 0000000..be8e66c --- /dev/null +++ b/example/network-engines/netty/src/jvmMain/kotlin/engines/NettyNetworkSocketEngine.kt @@ -0,0 +1,183 @@ +/* + * Copyright © 2026 RTAkland + * Author: RTAkland + * Date: 2026/9/8 + */ + +package engines + +import cn.rtast.libmc.network.RawSocket +import cn.rtast.libmc.network.ReadChannel +import cn.rtast.libmc.network.SocketEngine +import cn.rtast.libmc.network.WriteChannel +import io.netty.bootstrap.Bootstrap +import io.netty.buffer.ByteBuf +import io.netty.buffer.Unpooled +import io.netty.channel.* +import io.netty.channel.nio.NioIoHandler +import io.netty.channel.socket.SocketChannel +import io.netty.channel.socket.nio.NioSocketChannel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.withContext +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import io.netty.channel.Channel as NettyChannel +import kotlinx.coroutines.channels.Channel as KotlinChannel + +public class NettyNetworkSocketEngine : SocketEngine { + override fun create(host: String, port: Int): RawSocket = NettyNetworkSocket(host, port) + + private class NettyNetworkSocket(private val host: String, private val port: Int) : RawSocket { + private val workerGroup: EventLoopGroup = MultiThreadIoEventLoopGroup(0, NioIoHandler.newFactory()) + private lateinit var channel: NettyChannel + private val inboundQueue = KotlinChannel(KotlinChannel.UNLIMITED) + private lateinit var readChannel: NettyReadChannel + private lateinit var writeChannel: NettyWriteChannel + + override suspend fun connect() { + val bootstrap = Bootstrap() + .group(workerGroup) + .channel(NioSocketChannel::class.java) + .option(ChannelOption.TCP_NODELAY, true) + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000) + .handler(object : ChannelInitializer() { + override fun initChannel(ch: SocketChannel) { + ch.pipeline().addLast(object : ChannelInboundHandlerAdapter() { + override fun channelRead(ctx: ChannelHandlerContext, msg: Any) { + if (msg is ByteBuf) { + try { + val bytes = ByteArray(msg.readableBytes()) + msg.readBytes(bytes) + inboundQueue.trySend(bytes) + } finally { + msg.release() + } + } + } + + override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { + inboundQueue.close(cause) + ctx.close() + } + + override fun channelInactive(ctx: ChannelHandlerContext) { + inboundQueue.close() + super.channelInactive(ctx) + } + }) + } + }) + + channel = withContext(Dispatchers.IO) { + bootstrap.connect(host, port).suspendAwait() + } + + readChannel = NettyReadChannel(inboundQueue) + writeChannel = NettyWriteChannel(channel) + } + + override fun openReadChannel(): ReadChannel = readChannel + override fun openWriteChannel(): WriteChannel = writeChannel + + override fun close() { + if (::channel.isInitialized && channel.isOpen) { + channel.close() + } + inboundQueue.close() + workerGroup.shutdownGracefully() + } + } + + private class NettyReadChannel(private val inboundQueue: KotlinChannel) : ReadChannel { + private var currentChunk: ByteArray? = null + private var chunkOffset = 0 + + private suspend fun fetchNextChunk() { + val next = inboundQueue.receiveCatching().getOrNull() + ?: throw IllegalStateException("Socket/Channel closed while reading") + currentChunk = next + chunkOffset = 0 + } + + override suspend fun readByte(): Byte { + while (currentChunk == null || chunkOffset >= currentChunk!!.size) { + fetchNextChunk() + } + val chunk = currentChunk!! + return chunk[chunkOffset++] + } + + override suspend fun readBytes(length: Int): ByteArray { + val result = ByteArray(length) + readFully(result, 0, length) + return result + } + + override suspend fun readFully(out: ByteArray, start: Int, end: Int) { + var written = start + + while (written < end) { + while (currentChunk == null || chunkOffset >= currentChunk!!.size) { + fetchNextChunk() + } + val chunk = currentChunk!! + val available = chunk.size - chunkOffset + val toCopy = minOf(available, end - written) + + chunk.copyInto( + out, + destinationOffset = written, + startIndex = chunkOffset, + endIndex = chunkOffset + toCopy + ) + chunkOffset += toCopy + written += toCopy + } + } + } + + private class NettyWriteChannel(private val channel: NettyChannel) : WriteChannel { + private val writeMutex = Mutex() + + override suspend fun writeFully(value: ByteArray, startIndex: Int, endIndex: Int) { + val length = endIndex - startIndex + if (length <= 0) return + + val nettyBuf = Unpooled.copiedBuffer(value, startIndex, length) + + writeMutex.withLock { + withContext(Dispatchers.IO) { + channel.writeAndFlush(nettyBuf).suspendAwait() + } + } + } + + override suspend fun flush() { + writeMutex.withLock { + withContext(Dispatchers.IO) { + channel.flush() + } + } + } + } +} + +private suspend inline fun ChannelFuture.suspendAwait(): NettyChannel = suspendCancellableCoroutine { cont -> + if (isDone) { + if (isSuccess) cont.resume(channel()) + else cont.resumeWithException(cause() ?: RuntimeException("Netty operation failed")) + return@suspendCancellableCoroutine + } + + addListener { future -> + if (future.isSuccess) { + cont.resume(channel()) + } else { + cont.resumeWithException(future.cause() ?: RuntimeException("Netty operation failed")) + } + } + cont.invokeOnCancellation { cancel(false) } +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ec768e4..af4cc30 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,8 +6,7 @@ coroutines-test = "1.11.0" kotlinx-coroutines = "1.11.0" ktor-core = "3.5.2" cryptography-core = "0.6.0" -kotlinx-atomicfu = "0.33.0" -#kotlinx-serialization = "1.11.0" +netty = "4.2.17.Final" [libraries] kotlinx-io = { module = "org.jetbrains.kotlinx:kotlinx-io-core", version.ref = "kotlinx-io" } @@ -21,12 +20,9 @@ ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "kto ktor-client-winhttp = { module = "io.ktor:ktor-client-winhttp", version.ref = "ktor-core" } ktor-client-curl = { module = "io.ktor:ktor-client-curl", version.ref = "ktor-core" } ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor-core" } -kotlinx-atomicfu = { module = "org.jetbrains.kotlinx:atomicfu", version.ref = "kotlinx-atomicfu" } -#kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinx-serialization" } -#kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" } +netty-handler = { module = "io.netty:netty-transport", version.ref = "netty" } +netty-buffer = { module = "io.netty:netty-buffer", version.ref = "netty" } [plugins] kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } maven-publish = { id = "maven-publish" } -kotlinx-atomicfu = { id = "org.jetbrains.kotlinx.atomicfu", version.ref = "kotlinx-atomicfu" } -#kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } diff --git a/libmc-common/build.gradle.kts b/libmc-common/build.gradle.kts index 9ec50b5..ba31105 100644 --- a/libmc-common/build.gradle.kts +++ b/libmc-common/build.gradle.kts @@ -18,10 +18,6 @@ kotlin { jvmMain.dependencies {} -// nativeMain.dependencies { -// implementation(libs.ktor.network) -// } - commonTest.dependencies { implementation(kotlin("test")) implementation(libs.kotlinx.coroutines.test) diff --git a/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/network/SocketContext.kt b/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/network/SocketContext.kt index c55218a..ad9938a 100644 --- a/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/network/SocketContext.kt +++ b/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/network/SocketContext.kt @@ -7,8 +7,14 @@ package cn.rtast.libmc.network +import cn.rtast.libmc.crypto.ProtocolContextBuilder + public abstract class SocketContext { public abstract val engine: SocketEngine public fun createSocket(host: String, port: Int): RawSocket = engine.create(host, port) -} \ No newline at end of file +} + +public fun (ProtocolContextBuilder.() -> Unit).withCustom( + block: ProtocolContextBuilder.() -> Unit, +): ProtocolContextBuilder.() -> Unit = { this@withCustom.invoke(this); this.block() } \ No newline at end of file diff --git a/libmc-protocol-context/build.gradle.kts b/libmc-protocol-context/build.gradle.kts index d68de22..ff03d6d 100644 --- a/libmc-protocol-context/build.gradle.kts +++ b/libmc-protocol-context/build.gradle.kts @@ -16,29 +16,6 @@ kotlin { implementation(libs.cryptography.core) implementation(libs.cryptography.provider.optimal) implementation(libs.ktor.client.core) - implementation(libs.ktor.network) - } - - jvmTest.dependencies { - implementation(libs.ktor.client.okhttp) - } - - linuxTest.dependencies { - implementation(libs.ktor.client.curl) - } - - mingwTest.dependencies { - implementation(libs.ktor.client.winhttp) - } - - appleTest.dependencies { - implementation(libs.ktor.client.darwin) - } - - commonTest.dependencies { - implementation(kotlin("test")) - implementation(project(":protocol")) - implementation(libs.kotlinx.coroutines.test) } } } \ No newline at end of file diff --git a/libmc-protocol-context/src/commonMain/kotlin/cn/rtast/libmc/protocol/crypto/AesCFB8Cipher.kt b/libmc-protocol-context/src/commonMain/kotlin/cn/rtast/libmc/protocol/context/AesCFB8Cipher.kt similarity index 97% rename from libmc-protocol-context/src/commonMain/kotlin/cn/rtast/libmc/protocol/crypto/AesCFB8Cipher.kt rename to libmc-protocol-context/src/commonMain/kotlin/cn/rtast/libmc/protocol/context/AesCFB8Cipher.kt index 8c99677..e276568 100644 --- a/libmc-protocol-context/src/commonMain/kotlin/cn/rtast/libmc/protocol/crypto/AesCFB8Cipher.kt +++ b/libmc-protocol-context/src/commonMain/kotlin/cn/rtast/libmc/protocol/context/AesCFB8Cipher.kt @@ -7,7 +7,7 @@ @file:OptIn(DelicateCryptographyApi::class) -package cn.rtast.libmc.protocol.crypto +package cn.rtast.libmc.protocol.context import cn.rtast.libmc.crypto.NetworkCipher import dev.whyoleg.cryptography.DelicateCryptographyApi diff --git a/libmc-protocol-context/src/commonMain/kotlin/cn/rtast/libmc/protocol/crypto/DefaultProtocolCryptoContext.kt b/libmc-protocol-context/src/commonMain/kotlin/cn/rtast/libmc/protocol/context/DefaultProtocolCryptoContext.kt similarity index 90% rename from libmc-protocol-context/src/commonMain/kotlin/cn/rtast/libmc/protocol/crypto/DefaultProtocolCryptoContext.kt rename to libmc-protocol-context/src/commonMain/kotlin/cn/rtast/libmc/protocol/context/DefaultProtocolCryptoContext.kt index 796b285..a32d521 100644 --- a/libmc-protocol-context/src/commonMain/kotlin/cn/rtast/libmc/protocol/crypto/DefaultProtocolCryptoContext.kt +++ b/libmc-protocol-context/src/commonMain/kotlin/cn/rtast/libmc/protocol/context/DefaultProtocolCryptoContext.kt @@ -5,7 +5,7 @@ */ -package cn.rtast.libmc.protocol.crypto +package cn.rtast.libmc.protocol.context import cn.rtast.libmc.crypto.AuthenticationProvider import cn.rtast.libmc.crypto.ProtocolContextBuilder @@ -15,7 +15,7 @@ import io.ktor.client.* import io.ktor.client.request.* import io.ktor.http.* -private val httpClient = HttpClient() +public val httpClient: HttpClient = HttpClient() public val DefaultProtocolContext: ProtocolContextBuilder.() -> Unit = { rsaEncryptor = RSA1024Encryptor { key, data -> rsaEncrypt(key, data) } @@ -28,5 +28,4 @@ public val DefaultProtocolContext: ProtocolContextBuilder.() -> Unit = { }.status require(status == HttpStatusCode.NoContent) } - socketEngine = KtorNetworkEngine() } \ No newline at end of file diff --git a/libmc-protocol-context/src/commonMain/kotlin/cn/rtast/libmc/protocol/crypto/RSAEncryotor.kt b/libmc-protocol-context/src/commonMain/kotlin/cn/rtast/libmc/protocol/context/RSAEncryotor.kt similarity index 94% rename from libmc-protocol-context/src/commonMain/kotlin/cn/rtast/libmc/protocol/crypto/RSAEncryotor.kt rename to libmc-protocol-context/src/commonMain/kotlin/cn/rtast/libmc/protocol/context/RSAEncryotor.kt index b2cb2c0..b1d7d6e 100644 --- a/libmc-protocol-context/src/commonMain/kotlin/cn/rtast/libmc/protocol/crypto/RSAEncryotor.kt +++ b/libmc-protocol-context/src/commonMain/kotlin/cn/rtast/libmc/protocol/context/RSAEncryotor.kt @@ -7,7 +7,7 @@ @file:OptIn(DelicateCryptographyApi::class) -package cn.rtast.libmc.protocol.crypto +package cn.rtast.libmc.protocol.context import dev.whyoleg.cryptography.CryptographyProvider import dev.whyoleg.cryptography.DelicateCryptographyApi diff --git a/libmc-protocol-context/src/commonMain/kotlin/cn/rtast/libmc/protocol/crypto/Sha1Hasher.kt b/libmc-protocol-context/src/commonMain/kotlin/cn/rtast/libmc/protocol/context/Sha1Hasher.kt similarity index 97% rename from libmc-protocol-context/src/commonMain/kotlin/cn/rtast/libmc/protocol/crypto/Sha1Hasher.kt rename to libmc-protocol-context/src/commonMain/kotlin/cn/rtast/libmc/protocol/context/Sha1Hasher.kt index 88e1c46..b655a9c 100644 --- a/libmc-protocol-context/src/commonMain/kotlin/cn/rtast/libmc/protocol/crypto/Sha1Hasher.kt +++ b/libmc-protocol-context/src/commonMain/kotlin/cn/rtast/libmc/protocol/context/Sha1Hasher.kt @@ -7,7 +7,7 @@ @file:OptIn(DelicateCryptographyApi::class) -package cn.rtast.libmc.protocol.crypto +package cn.rtast.libmc.protocol.context import dev.whyoleg.cryptography.DelicateCryptographyApi import dev.whyoleg.cryptography.algorithms.SHA1 diff --git a/libmc-protocol-context/src/jvmTest/kotlin/test/TestJvmClient.kt b/libmc-protocol-context/src/jvmTest/kotlin/test/TestJvmClient.kt index 4a67b41..144d188 100644 --- a/libmc-protocol-context/src/jvmTest/kotlin/test/TestJvmClient.kt +++ b/libmc-protocol-context/src/jvmTest/kotlin/test/TestJvmClient.kt @@ -8,7 +8,7 @@ package test import cn.rtast.libmc.packet.MinecraftPacket import cn.rtast.libmc.protocol.client.createMinecraftClient -import cn.rtast.libmc.protocol.crypto.DefaultProtocolContext +import cn.rtast.libmc.protocol.context.DefaultProtocolContext import kotlinx.coroutines.launch import org.junit.Test import java.io.File diff --git a/libmc-protocol/build.gradle.kts b/libmc-protocol/build.gradle.kts index 8ab3e52..ba86c42 100644 --- a/libmc-protocol/build.gradle.kts +++ b/libmc-protocol/build.gradle.kts @@ -24,6 +24,7 @@ kotlin { implementation(kotlin("test")) implementation(project(":protocol-context")) implementation(libs.kotlinx.coroutines.test) + implementation(libs.ktor.network) } jvmTest.dependencies { diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/client/MinecraftClient.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/client/MinecraftClient.kt index 702c646..f6e873d 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/client/MinecraftClient.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/client/MinecraftClient.kt @@ -97,9 +97,9 @@ public fun createMinecraftClient( accessToken: String?, parentJob: Job? = null, ioDispatcher: CoroutineDispatcher = Dispatchers.IO, - contextBuilder: ProtocolContextBuilder.() -> Unit, + context: ProtocolContextBuilder.() -> Unit, ): MinecraftClient { - val context = ProtocolContextBuilder(accessToken != null).apply(contextBuilder).build() + val context = ProtocolContextBuilder(accessToken != null).apply(context).build() return MinecraftClient( host = host, port = port, diff --git a/libmc-protocol-context/src/commonMain/kotlin/cn/rtast/libmc/protocol/crypto/KtorNetworkEngine.kt b/libmc-protocol/src/commonTest/kotlin/test/KtorNetworkEngine.kt similarity index 80% rename from libmc-protocol-context/src/commonMain/kotlin/cn/rtast/libmc/protocol/crypto/KtorNetworkEngine.kt rename to libmc-protocol/src/commonTest/kotlin/test/KtorNetworkEngine.kt index a08466e..90716db 100644 --- a/libmc-protocol-context/src/commonMain/kotlin/cn/rtast/libmc/protocol/crypto/KtorNetworkEngine.kt +++ b/libmc-protocol/src/commonTest/kotlin/test/KtorNetworkEngine.kt @@ -5,7 +5,7 @@ */ -package cn.rtast.libmc.protocol.crypto +package test import cn.rtast.libmc.network.RawSocket import cn.rtast.libmc.network.ReadChannel @@ -17,11 +17,11 @@ import io.ktor.utils.io.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.IO -public class KtorNetworkEngine : SocketEngine { +class KtorNetworkEngine : SocketEngine { override fun create(host: String, port: Int): RawSocket = KtorNetworkSocket(host, port) } -public class KtorNetworkSocket(private val host: String, private val port: Int) : RawSocket { +class KtorNetworkSocket(private val host: String, private val port: Int) : RawSocket { private val sm = SelectorManager(Dispatchers.IO) private lateinit var socket: Socket @@ -34,14 +34,14 @@ public class KtorNetworkSocket(private val host: String, private val port: Int) } } -public class KtorReadChannel(private val readChannel: ByteReadChannel) : ReadChannel { +class KtorReadChannel(private val readChannel: ByteReadChannel) : ReadChannel { override suspend fun readByte(): Byte = readChannel.readByte() override suspend fun readBytes(length: Int): ByteArray = readChannel.readByteArray(length) override suspend fun readFully(out: ByteArray, start: Int, end: Int): Unit = readChannel.readFully(out, start, end) } -public class KtorWriteChannel(private val writeChannel: ByteWriteChannel) : WriteChannel { +class KtorWriteChannel(private val writeChannel: ByteWriteChannel) : WriteChannel { override suspend fun writeFully(value: ByteArray, startIndex: Int, endIndex: Int) { writeChannel.writeFully(value, startIndex, endIndex) } diff --git a/libmc-protocol/src/jvmTest/kotlin/test/TestClientTestInJvm.kt b/libmc-protocol/src/jvmTest/kotlin/test/TestClientTestInJvm.kt index 9d4933f..b56e515 100644 --- a/libmc-protocol/src/jvmTest/kotlin/test/TestClientTestInJvm.kt +++ b/libmc-protocol/src/jvmTest/kotlin/test/TestClientTestInJvm.kt @@ -7,9 +7,10 @@ package test +import cn.rtast.libmc.network.withCustom import cn.rtast.libmc.packet.ClientboundUnknownPacket import cn.rtast.libmc.protocol.client.createMinecraftClient -import cn.rtast.libmc.protocol.crypto.DefaultProtocolContext +import cn.rtast.libmc.protocol.context.DefaultProtocolContext import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundPlayerChatMessagePacket import cn.rtast.libmc.protocol.packet.play.serverbound.ServerboundChatMessagePacket import cn.rtast.libmc.protocol.util.generateOfflineUuid @@ -63,7 +64,7 @@ class TestClientTestInJvm { Uuid.parse("bb033844-e68e-4909-a636-1a5d1821ddc4"), // null, accessToken, - contextBuilder = DefaultProtocolContext + context = DefaultProtocolContext ) // cli.onPacket { println(it) } // cli.onPacket { println(it) } @@ -81,8 +82,11 @@ class TestClientTestInJvm { "11", generateOfflineUuid("11"), null, - contextBuilder = DefaultProtocolContext + context = DefaultProtocolContext.withCustom { + socketEngine = KtorNetworkEngine() + } ) + // cli.on { packet, direction -> // if (packet !is ClientboundWaypointPacket) // println("$direction -> $packet") diff --git a/settings.gradle.kts b/settings.gradle.kts index e7b81d7..65df10a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -4,12 +4,16 @@ plugins { rootProject.name = "libmc" -includeSubModule(":common") -includeSubModule(":protocol") -includeSubModule(":protocol-context") -includeSubModule(":nbt") -//includeSubModule(":snbt") +includeSubModule("common") +includeSubModule("protocol") +includeSubModule("protocol-context") +includeSubModule("nbt") +includeSubModule("snbt") -fun includeSubModule(name: String, path: String? = null) = include(name).also { - project(name).projectDir = file(path ?: "libmc-${name.removePrefix(":")}") -} +//includeSubModule("protocol-engine-netty", path = "libmc-network-engines/netty") +//includeSubModule("protocol-engine-ktor-network", path = "libmc-network-engines/ktor-network") + +fun includeSubModule(name: String, path: String? = null) { + include(":$name") + project(":$name").projectDir = file(path ?: "libmc-$name") +} \ No newline at end of file