commit cdfc11344d4860bc60a58c261c99556b262ecd56 Author: Moritz Ruth Date: Sat Aug 1 23:55:31 2020 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3ce0157 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +# Project exclude paths +/.gradle/ +/blokk-api/build/ +/blokk-server/build/ \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d4f20b6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 The Blokk contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/blokk-api/build.gradle.kts b/blokk-api/build.gradle.kts new file mode 100644 index 0000000..529e3e8 --- /dev/null +++ b/blokk-api/build.gradle.kts @@ -0,0 +1,41 @@ +plugins { + kotlin("jvm") +} + +group = rootProject.group +version = rootProject.version + +repositories { + mavenCentral() + jcenter() +} + +val spekVersion = "2.0.12" + +dependencies { + implementation(kotlin("stdlib-jdk8")) + implementation("com.google.code.gson:gson:2.8.6") + api("org.slf4j:slf4j-api:1.7.30") + api("io.netty:netty-buffer:4.1.50.Final") + + testImplementation("io.strikt:strikt-core:0.26.1") + testImplementation("org.spekframework.spek2:spek-dsl-jvm:$spekVersion") + testRuntimeOnly("org.spekframework.spek2:spek-runner-junit5:$spekVersion") + testRuntimeOnly(kotlin("reflect")) +} + +tasks { + compileKotlin { + kotlinOptions.jvmTarget = "1.8" + } + + compileTestKotlin { + kotlinOptions.jvmTarget = "1.8" + } + + test { + useJUnitPlatform { + includeEngines("spek2") + } + } +} diff --git a/blokk-api/src/main/kotlin/space/blokk/Blokk.kt b/blokk-api/src/main/kotlin/space/blokk/Blokk.kt new file mode 100644 index 0000000..6579147 --- /dev/null +++ b/blokk-api/src/main/kotlin/space/blokk/Blokk.kt @@ -0,0 +1,5 @@ +package space.blokk + +object Blokk { + lateinit var server: Server +} diff --git a/blokk-api/src/main/kotlin/space/blokk/Server.kt b/blokk-api/src/main/kotlin/space/blokk/Server.kt new file mode 100644 index 0000000..63c618b --- /dev/null +++ b/blokk-api/src/main/kotlin/space/blokk/Server.kt @@ -0,0 +1,7 @@ +package space.blokk + +import space.blokk.utils.Logger + +interface Server { + val logger: Logger +} diff --git a/blokk-api/src/main/kotlin/space/blokk/chat/ChatComponent.kt b/blokk-api/src/main/kotlin/space/blokk/chat/ChatComponent.kt new file mode 100644 index 0000000..b71daec --- /dev/null +++ b/blokk-api/src/main/kotlin/space/blokk/chat/ChatComponent.kt @@ -0,0 +1,24 @@ +package space.blokk.chat + +import kotlin.reflect.KClass + +sealed class ChatComponent { + abstract val extra: ChatComponent? + + fun getExtraTypes(): List> { + val types = mutableListOf>() + var current = extra + while (current != null) { + types.add(current::class) + current = current.extra + } + + return types.toList() + } +} + +data class TextComponent(val text: String, override val extra: ChatComponent? = null): ChatComponent() { + companion object { + infix fun of(text: String) = TextComponent(text) + } +} diff --git a/blokk-api/src/main/kotlin/space/blokk/chat/FormattingCode.kt b/blokk-api/src/main/kotlin/space/blokk/chat/FormattingCode.kt new file mode 100644 index 0000000..d66290f --- /dev/null +++ b/blokk-api/src/main/kotlin/space/blokk/chat/FormattingCode.kt @@ -0,0 +1,33 @@ +package space.blokk.chat + +/** + * Legacy formatting codes. You should use [space.blokk.chat.ChatComponent] whenever it it possible, but sometimes + * these codes are required, for example in the name of a player in the server list sample + * ([space.blokk.net.protocols.status.ResponsePacket.Players.SampleEntry]). + */ +enum class FormattingCode(private val char: Char) { + BLACK('0'), + DARK_BLUE('1'), + DARK_GREEN('2'), + DARK_AQUA('3'), + DARK_RED('4'), + DARK_PURPLE('5'), + GOLD('6'), + GRAY('7'), + DARK_GRAY('8'), + BLUE('9'), + GREEN('a'), + AQUA('b'), + RED('c'), + LIGHT_PURPLE('d'), + YELLOW('e'), + WHITE('f'), + OBFUSCATED('k'), + BOLD('l'), + STRIKETHROUGH('m'), + UNDERLINE('n'), + ITALIC('o'), + RESET('r'); + + override fun toString(): String = "ยง$char" +} diff --git a/blokk-api/src/main/kotlin/space/blokk/events/Cancellable.kt b/blokk-api/src/main/kotlin/space/blokk/events/Cancellable.kt new file mode 100644 index 0000000..8c9cc0a --- /dev/null +++ b/blokk-api/src/main/kotlin/space/blokk/events/Cancellable.kt @@ -0,0 +1,5 @@ +package space.blokk.events + +interface Cancellable { + var isCancelled: Boolean +} diff --git a/blokk-api/src/main/kotlin/space/blokk/events/Event.kt b/blokk-api/src/main/kotlin/space/blokk/events/Event.kt new file mode 100644 index 0000000..d9b2c94 --- /dev/null +++ b/blokk-api/src/main/kotlin/space/blokk/events/Event.kt @@ -0,0 +1,4 @@ +package space.blokk.events + +abstract class Event { +} diff --git a/blokk-api/src/main/kotlin/space/blokk/events/EventBus.kt b/blokk-api/src/main/kotlin/space/blokk/events/EventBus.kt new file mode 100644 index 0000000..30b1413 --- /dev/null +++ b/blokk-api/src/main/kotlin/space/blokk/events/EventBus.kt @@ -0,0 +1,51 @@ +package space.blokk.events + +import space.blokk.plugins.Plugin +import java.lang.reflect.Method +import kotlin.reflect.KClass + +class EventBus(eventType: KClass) { + private val eventType: Class = eventType.java + + /** + * All event handlers, sorted by their priority and the order in which they were inserted + */ + private val handlers = mutableListOf() + + fun emit(event: EventT) { + handlers.filter { it.eventType.isInstance(event) }.forEach { it.fn.invoke(it.listener, event) } + } + + fun register(listener: Listener) { + val handlersOfListener = listener::class.java.methods + .mapNotNull { method -> method.getAnnotation(EventHandler::class.java)?.let { method to it } } + .toMap() + + for ((method, data) in handlersOfListener) { + if (method.parameters.count() != 1) + throw InvalidEventHandlerException("${method.name} must have exactly one parameter") + + val type = method.parameterTypes[0] + if (!eventType.isAssignableFrom(type)) + throw InvalidEventHandlerException("${method.name}'s first parameter type is incompatible with the " + + "one required by the EventBus") + + @Suppress("UNCHECKED_CAST") + val handler = Handler(type as Class, listener, method, Plugin.getCalling(), data.priority) + + val insertIndex = handlers.indexOfLast { it.priority.ordinal <= handler.priority.ordinal } + 1 + handlers.add(insertIndex, handler) + println(handlers) + } + } + + class InvalidEventHandlerException(message: String): Exception(message) + + private data class Handler( + val eventType: Class, + val listener: Listener, + val fn: Method, + val plugin: Plugin?, + val priority: EventPriority + ) +} diff --git a/blokk-api/src/main/kotlin/space/blokk/events/EventHandler.kt b/blokk-api/src/main/kotlin/space/blokk/events/EventHandler.kt new file mode 100644 index 0000000..a941505 --- /dev/null +++ b/blokk-api/src/main/kotlin/space/blokk/events/EventHandler.kt @@ -0,0 +1,3 @@ +package space.blokk.events + +annotation class EventHandler(val priority: EventPriority = EventPriority.NORMAL) diff --git a/blokk-api/src/main/kotlin/space/blokk/events/EventPriority.kt b/blokk-api/src/main/kotlin/space/blokk/events/EventPriority.kt new file mode 100644 index 0000000..b7a1e7a --- /dev/null +++ b/blokk-api/src/main/kotlin/space/blokk/events/EventPriority.kt @@ -0,0 +1,12 @@ +package space.blokk.events + +enum class EventPriority { + LOWEST, + LOW, + LOWER, + NORMAL, + HIGHER, + HIGH, + HIGHEST, + MONITOR +} diff --git a/blokk-api/src/main/kotlin/space/blokk/events/Listener.kt b/blokk-api/src/main/kotlin/space/blokk/events/Listener.kt new file mode 100644 index 0000000..38e55b1 --- /dev/null +++ b/blokk-api/src/main/kotlin/space/blokk/events/Listener.kt @@ -0,0 +1,3 @@ +package space.blokk.events + +interface Listener diff --git a/blokk-api/src/main/kotlin/space/blokk/net/ByteBufExtensions.kt b/blokk-api/src/main/kotlin/space/blokk/net/ByteBufExtensions.kt new file mode 100644 index 0000000..c42ba7e --- /dev/null +++ b/blokk-api/src/main/kotlin/space/blokk/net/ByteBufExtensions.kt @@ -0,0 +1,70 @@ +package space.blokk.net + +import io.netty.buffer.ByteBuf +import java.io.IOException +import kotlin.experimental.and + +fun ByteBuf.readVarInt(): Int { + var bytesRead = 0 + var result = 0 + + do { + if (bytesRead == 5) throw IOException("VarInt is longer than the maximum of 5 bytes") + + val byte = readByte() + val value: Int = (byte and 0b01111111).toInt() + result = result or (value shl (7 * bytesRead)) + bytesRead += 1 + } while ((byte and 0b10000000.toByte()).toInt() != 0) + + return result +} + +fun ByteBuf.varIntReadable(): Boolean { + if (readableBytes() > 5) { + // maximum VarInt size + return true + } + + val initialIndex = readerIndex() + do { + if (readableBytes() < 1) { + readerIndex(initialIndex) + return false; + } + + val value = readByte().toInt() + } while ((value and 0b10000000) != 0); + + readerIndex(initialIndex) + return true; +} + +fun ByteBuf.writeVarInt(value: Int) { + var v = value + var part: Byte + while (true) { + part = (v and 0x7F).toByte() + v = v ushr 7 + if (v != 0) { + part = (part.toInt() or 0x80).toByte() + } + writeByte(part.toInt()) + if (v == 0) { + break + } + } +} + +fun ByteBuf.readString(): String { + val length = readVarInt() + val bytes = ByteArray(length) + readBytes(bytes) + return bytes.toString(Charsets.UTF_8) +} + +fun ByteBuf.writeString(value: String) { + val bytes = value.toByteArray(Charsets.UTF_8) + writeVarInt(bytes.size) + writeBytes(bytes) +} diff --git a/blokk-api/src/main/kotlin/space/blokk/net/PacketReceivedEvent.kt b/blokk-api/src/main/kotlin/space/blokk/net/PacketReceivedEvent.kt new file mode 100644 index 0000000..98f352f --- /dev/null +++ b/blokk-api/src/main/kotlin/space/blokk/net/PacketReceivedEvent.kt @@ -0,0 +1,7 @@ +package space.blokk.net + +import space.blokk.events.Cancellable + +open class PacketReceivedEvent: SessionEvent(), Cancellable { + override var isCancelled = false +} diff --git a/blokk-api/src/main/kotlin/space/blokk/net/PacketWithResponseReceivedEvent.kt b/blokk-api/src/main/kotlin/space/blokk/net/PacketWithResponseReceivedEvent.kt new file mode 100644 index 0000000..9ec8d55 --- /dev/null +++ b/blokk-api/src/main/kotlin/space/blokk/net/PacketWithResponseReceivedEvent.kt @@ -0,0 +1,7 @@ +package space.blokk.net + +import space.blokk.net.protocols.ClientboundPacket + +class PacketWithResponseReceivedEvent( + var response: ResponseT +): PacketReceivedEvent() diff --git a/blokk-api/src/main/kotlin/space/blokk/net/Session.kt b/blokk-api/src/main/kotlin/space/blokk/net/Session.kt new file mode 100644 index 0000000..4dcb7f1 --- /dev/null +++ b/blokk-api/src/main/kotlin/space/blokk/net/Session.kt @@ -0,0 +1,18 @@ +package space.blokk.net + +import space.blokk.events.Event +import space.blokk.events.EventBus +import space.blokk.net.protocols.ClientboundPacket +import space.blokk.net.protocols.Protocol +import java.net.InetAddress + +interface Session { + var currentProtocol: Protocol + val address: InetAddress + + fun send(packet: ClientboundPacket) + + val eventsManager: EventBus +} + +open class SessionEvent: Event() diff --git a/blokk-api/src/main/kotlin/space/blokk/net/protocols/Packet.kt b/blokk-api/src/main/kotlin/space/blokk/net/protocols/Packet.kt new file mode 100644 index 0000000..e60e8be --- /dev/null +++ b/blokk-api/src/main/kotlin/space/blokk/net/protocols/Packet.kt @@ -0,0 +1,29 @@ +package space.blokk.net.protocols + +import io.netty.buffer.ByteBuf +import space.blokk.net.Session +import kotlin.reflect.KClass + +abstract class Packet +abstract class ServerboundPacket: Packet() { + abstract fun handle(session: Session) +} + +abstract class ClientboundPacket: Packet() { + abstract fun encode(dst: ByteBuf) +} + +sealed class PacketCompanion(val id: Int, val packetType: KClass) + +abstract class ServerboundPacketCompanion( + id: Int, + packetType: KClass +): PacketCompanion(id, packetType) { + abstract fun decode(msg: ByteBuf): T +} + +abstract class ClientboundPacketCompanion( + id: Int, + packetType: KClass +): PacketCompanion(id, packetType) + diff --git a/blokk-api/src/main/kotlin/space/blokk/net/protocols/Protocol.kt b/blokk-api/src/main/kotlin/space/blokk/net/protocols/Protocol.kt new file mode 100644 index 0000000..9398857 --- /dev/null +++ b/blokk-api/src/main/kotlin/space/blokk/net/protocols/Protocol.kt @@ -0,0 +1,25 @@ +package space.blokk.net.protocols + +abstract class Protocol(packets: Set>) { + val serverboundPackets = packets.filterIsInstance>() + val serverboundPacketsByID = serverboundPackets.mapToIDMap() + val clientboundPackets = packets.filterIsInstance>() + val clientboundPacketsById = clientboundPackets.mapToIDMap() + val packetCompanionsByPacketType = packets.map { it.packetType to it }.toMap() + + private fun > Iterable.mapToIDMap() = map { it.id to it }.toMap() + + fun validate() { + ensureDistinctIDs(serverboundPackets, "serverbound") + ensureDistinctIDs(clientboundPackets, "clientbound") + } + + private fun ensureDistinctIDs(packets: Iterable>, type: String) { + packets.groupBy { it.id }.forEach { (id, p) -> + if (p.count() > 1) { + val packetsString = p.joinToString(", ", limit = 4) { it.packetType.simpleName.toString() } + throw Error("Multiple $type packets use the same ID (0x${id.toString(16)}): $packetsString") + } + } + } +} diff --git a/blokk-api/src/main/kotlin/space/blokk/net/protocols/Protocols.kt b/blokk-api/src/main/kotlin/space/blokk/net/protocols/Protocols.kt new file mode 100644 index 0000000..3411d55 --- /dev/null +++ b/blokk-api/src/main/kotlin/space/blokk/net/protocols/Protocols.kt @@ -0,0 +1,10 @@ +package space.blokk.net.protocols + +import space.blokk.net.protocols.handshaking.HandshakingProtocol +import space.blokk.net.protocols.login.LoginProtocol +import space.blokk.net.protocols.status.StatusProtocol + +object Protocols { + val all = setOf(HandshakingProtocol, StatusProtocol, LoginProtocol) + fun validate() = all.forEach(Protocol::validate) +} diff --git a/blokk-api/src/main/kotlin/space/blokk/net/protocols/handshaking/HandshakePacket.kt b/blokk-api/src/main/kotlin/space/blokk/net/protocols/handshaking/HandshakePacket.kt new file mode 100644 index 0000000..8ed653f --- /dev/null +++ b/blokk-api/src/main/kotlin/space/blokk/net/protocols/handshaking/HandshakePacket.kt @@ -0,0 +1,39 @@ +package space.blokk.net.protocols.handshaking + +import io.netty.buffer.ByteBuf +import space.blokk.Blokk +import space.blokk.net.PacketReceivedEvent +import space.blokk.net.Session +import space.blokk.net.protocols.ServerboundPacket +import space.blokk.net.protocols.ServerboundPacketCompanion +import space.blokk.net.protocols.login.LoginProtocol +import space.blokk.net.protocols.status.StatusProtocol +import space.blokk.net.readString +import space.blokk.net.readVarInt + +data class HandshakePacket( + val protocolVersion: Int, + val serverAddress: String, + val serverPort: Int, + val login: Boolean +): ServerboundPacket() { + override fun handle(session: Session) { + val event = PacketReceivedEvent() + session.eventsManager.emit(event) + if (!event.isCancelled) { + Blokk.server.logger.debug { "${session.address.hostAddress} initiated handshake. Is login: $login" } + session.currentProtocol = if (login) LoginProtocol else StatusProtocol + } + } + + companion object: ServerboundPacketCompanion(0x00, HandshakePacket::class) { + override fun decode(msg: ByteBuf): HandshakePacket { + return HandshakePacket( + protocolVersion = msg.readVarInt(), + serverAddress = msg.readString(), + serverPort = msg.readUnsignedShort(), + login = msg.readVarInt() == 2 + ) + } + } +} diff --git a/blokk-api/src/main/kotlin/space/blokk/net/protocols/handshaking/HandshakingProtocol.kt b/blokk-api/src/main/kotlin/space/blokk/net/protocols/handshaking/HandshakingProtocol.kt new file mode 100644 index 0000000..a2aaf90 --- /dev/null +++ b/blokk-api/src/main/kotlin/space/blokk/net/protocols/handshaking/HandshakingProtocol.kt @@ -0,0 +1,5 @@ +package space.blokk.net.protocols.handshaking + +import space.blokk.net.protocols.Protocol + +object HandshakingProtocol : Protocol(setOf(HandshakePacket)) diff --git a/blokk-api/src/main/kotlin/space/blokk/net/protocols/login/LoginProtocol.kt b/blokk-api/src/main/kotlin/space/blokk/net/protocols/login/LoginProtocol.kt new file mode 100644 index 0000000..50fac33 --- /dev/null +++ b/blokk-api/src/main/kotlin/space/blokk/net/protocols/login/LoginProtocol.kt @@ -0,0 +1,6 @@ +package space.blokk.net.protocols.login + +import space.blokk.net.protocols.Protocol + +object LoginProtocol: Protocol(setOf()) { +} diff --git a/blokk-api/src/main/kotlin/space/blokk/net/protocols/status/PingPacket.kt b/blokk-api/src/main/kotlin/space/blokk/net/protocols/status/PingPacket.kt new file mode 100644 index 0000000..dac57ea --- /dev/null +++ b/blokk-api/src/main/kotlin/space/blokk/net/protocols/status/PingPacket.kt @@ -0,0 +1,18 @@ +package space.blokk.net.protocols.status + +import io.netty.buffer.ByteBuf +import space.blokk.net.Session +import space.blokk.net.protocols.ServerboundPacket +import space.blokk.net.protocols.ServerboundPacketCompanion + +data class PingPacket(val payload: Long) : ServerboundPacket() { + override fun handle(session: Session) { + session.send(PongPacket(payload)) + } + + companion object : ServerboundPacketCompanion(0x01, PingPacket::class) { + override fun decode(msg: ByteBuf): PingPacket { + return PingPacket(msg.readLong()) + } + } +} diff --git a/blokk-api/src/main/kotlin/space/blokk/net/protocols/status/PongPacket.kt b/blokk-api/src/main/kotlin/space/blokk/net/protocols/status/PongPacket.kt new file mode 100644 index 0000000..749208e --- /dev/null +++ b/blokk-api/src/main/kotlin/space/blokk/net/protocols/status/PongPacket.kt @@ -0,0 +1,13 @@ +package space.blokk.net.protocols.status + +import io.netty.buffer.ByteBuf +import space.blokk.net.protocols.ClientboundPacket +import space.blokk.net.protocols.ClientboundPacketCompanion + +data class PongPacket(val payload: Long) : ClientboundPacket() { + override fun encode(dst: ByteBuf) { + dst.writeLong(payload) + } + + companion object : ClientboundPacketCompanion(0x01, PongPacket::class) +} diff --git a/blokk-api/src/main/kotlin/space/blokk/net/protocols/status/RequestPacket.kt b/blokk-api/src/main/kotlin/space/blokk/net/protocols/status/RequestPacket.kt new file mode 100644 index 0000000..3e6bd88 --- /dev/null +++ b/blokk-api/src/main/kotlin/space/blokk/net/protocols/status/RequestPacket.kt @@ -0,0 +1,29 @@ +package space.blokk.net.protocols.status + +import io.netty.buffer.ByteBuf +import space.blokk.chat.FormattingCode +import space.blokk.chat.TextComponent +import space.blokk.net.Session +import space.blokk.net.protocols.ServerboundPacket +import space.blokk.net.protocols.ServerboundPacketCompanion +import java.util.* + +class RequestPacket: ServerboundPacket() { + override fun handle(session: Session) { + // TODO: Respond with the correct data + session.send(ResponsePacket( + versionName = "1.15.2", + protocolVersion = 578, + description = TextComponent of "nice", + players = ResponsePacket.Players( + 10, + 10, + listOf(ResponsePacket.Players.SampleEntry("${FormattingCode.AQUA}Gronkh", UUID.randomUUID().toString()) + )) + )) + } + + companion object : ServerboundPacketCompanion(0x00, RequestPacket::class) { + override fun decode(msg: ByteBuf): RequestPacket = RequestPacket() + } +} diff --git a/blokk-api/src/main/kotlin/space/blokk/net/protocols/status/ResponsePacket.kt b/blokk-api/src/main/kotlin/space/blokk/net/protocols/status/ResponsePacket.kt new file mode 100644 index 0000000..4a2ed3e --- /dev/null +++ b/blokk-api/src/main/kotlin/space/blokk/net/protocols/status/ResponsePacket.kt @@ -0,0 +1,50 @@ +package space.blokk.net.protocols.status + +import com.google.gson.GsonBuilder +import io.netty.buffer.ByteBuf +import space.blokk.chat.TextComponent +import space.blokk.net.protocols.ClientboundPacket +import space.blokk.net.protocols.ClientboundPacketCompanion +import space.blokk.net.writeString +import java.util.* + +data class ResponsePacket( + val versionName: String, + val protocolVersion: Int, + val description: TextComponent, + val players: Players, + val favicon: String? = null +) : ClientboundPacket() { + constructor( + versionName: String, + protocolVersion: Int, + players: Players, + description: TextComponent, + favicon: ByteArray + ): this(versionName, protocolVersion, description, players, Base64.getEncoder().encodeToString(favicon)) + + init { + if (description.getExtraTypes().find { it != TextComponent::class } != null) + throw Exception("description may only contain instances of TextComponent") + } + + override fun encode(dst: ByteBuf) { + dst.writeString(gson.toJson(mapOf( + "version" to mapOf( + "name" to versionName, + "protocol" to protocolVersion + ), + "players" to players, + "description" to description, + "favicon" to favicon + ))) + } + + data class Players(val max: Int, val online: Int, val sample: List) { + data class SampleEntry(val name: String, val id: String) + } + + companion object : ClientboundPacketCompanion(0x00, ResponsePacket::class) { + private val gson = GsonBuilder().disableHtmlEscaping().create()!! + } +} diff --git a/blokk-api/src/main/kotlin/space/blokk/net/protocols/status/StatusProtocol.kt b/blokk-api/src/main/kotlin/space/blokk/net/protocols/status/StatusProtocol.kt new file mode 100644 index 0000000..491fd4c --- /dev/null +++ b/blokk-api/src/main/kotlin/space/blokk/net/protocols/status/StatusProtocol.kt @@ -0,0 +1,5 @@ +package space.blokk.net.protocols.status + +import space.blokk.net.protocols.Protocol + +object StatusProtocol: Protocol(setOf(RequestPacket, ResponsePacket, PingPacket, PongPacket)) diff --git a/blokk-api/src/main/kotlin/space/blokk/plugins/Plugin.kt b/blokk-api/src/main/kotlin/space/blokk/plugins/Plugin.kt new file mode 100644 index 0000000..8108961 --- /dev/null +++ b/blokk-api/src/main/kotlin/space/blokk/plugins/Plugin.kt @@ -0,0 +1,11 @@ +package space.blokk.plugins + +interface Plugin { + companion object { + fun getCalling(): Plugin? { + // TODO: Return the last plugin in the stacktrace + // This could be useful: https://stackoverflow.com/questions/421280/how-do-i-find-the-caller-of-a-method-using-stacktrace-or-reflection + return null + } + } +} diff --git a/blokk-api/src/main/kotlin/space/blokk/utils/Logger.kt b/blokk-api/src/main/kotlin/space/blokk/utils/Logger.kt new file mode 100644 index 0000000..6841dde --- /dev/null +++ b/blokk-api/src/main/kotlin/space/blokk/utils/Logger.kt @@ -0,0 +1,33 @@ +package space.blokk.utils + +import org.slf4j.LoggerFactory + +class Logger(name: String) { + private val logger = LoggerFactory.getLogger(name) + + infix fun error(msg: String) = logger.error(msg) + infix fun info(msg: String) = logger.info(msg) + infix fun warn(msg: String) = logger.warn(msg) + infix fun debug(msg: String) = logger.debug(msg) + infix fun trace(msg: String) = logger.trace(msg) + + fun error(fn: () -> String) { + if (logger.isErrorEnabled) logger.error(fn()) + } + + fun info(fn: () -> String) { + if (logger.isErrorEnabled) logger.info(fn()) + } + + fun warn(fn: () -> String) { + if (logger.isErrorEnabled) logger.warn(fn()) + } + + fun debug(fn: () -> String) { + if (logger.isErrorEnabled) logger.debug(fn()) + } + + fun trace(fn: () -> String) { + if (logger.isErrorEnabled) logger.trace(fn()) + } +} diff --git a/blokk-api/src/test/kotlin/EventBusTest.kt b/blokk-api/src/test/kotlin/EventBusTest.kt new file mode 100644 index 0000000..546b0e0 --- /dev/null +++ b/blokk-api/src/test/kotlin/EventBusTest.kt @@ -0,0 +1,110 @@ + +import org.spekframework.spek2.Spek +import org.spekframework.spek2.style.specification.describe +import space.blokk.events.* +import strikt.api.expectThat +import strikt.api.expectThrows +import strikt.assertions.isEqualTo +import strikt.assertions.isFalse +import strikt.assertions.isSorted +import strikt.assertions.isTrue + +private abstract class TestEvent: Event() +private class FirstEvent: TestEvent() +private class SecondEvent: TestEvent() + +object EventBusTest: Spek({ + describe("EventBus") { + val eventBus by memoized { EventBus(TestEvent::class) } + + it("calls the handler exactly 1 time when the event is emitted 1 time") { + var calledCount = 0 + eventBus.register(object : Listener { + @EventHandler fun onFirstEvent(event: FirstEvent) { calledCount++ } + }) + + eventBus.emit(FirstEvent()) + expectThat(calledCount).isEqualTo(1) + } + + it("calls the handler 3 times when the event is emitted 3 times") { + var calledCount = 0 + eventBus.register(object : Listener { + @EventHandler fun onFirstEvent(event: FirstEvent) { calledCount++ } + }) + + eventBus.emit(FirstEvent()) + eventBus.emit(FirstEvent()) + eventBus.emit(FirstEvent()) + + expectThat(calledCount).isEqualTo(3) + } + + it("calls the handlers for super types of the emitted event") { + var onTestEventCalled = false + var onFirstEventCalled = false + eventBus.register(object : Listener { + @EventHandler fun onTestEvent(event: TestEvent) { onTestEventCalled = true } + @EventHandler fun onFirstEvent(event: FirstEvent) { onFirstEventCalled = true } + }) + + eventBus.emit(FirstEvent()) + + expectThat(onTestEventCalled).isTrue() + expectThat(onFirstEventCalled).isTrue() + } + + it("calls no handlers if no event is emitted") { + var onFirstEventCalled = false + var onSecondEventCalled = false + eventBus.register(object : Listener { + @EventHandler fun onFirstEvent(event: FirstEvent) { onFirstEventCalled = true } + @EventHandler fun onSecondEvent(event: SecondEvent) { onSecondEventCalled = true } + }) + + // No emit + + expectThat(onFirstEventCalled).isFalse() + expectThat(onSecondEventCalled).isFalse() + } + + it("throws an error if an event handler function has an invalid signature") { + expectThrows { + eventBus.register(object : Listener { + @EventHandler fun onEvent(coolParam: String) {} + }) + } + + expectThrows { + eventBus.register(object : Listener { + @EventHandler fun onFirstEvent(coolParam: String, event: FirstEvent) {} + }) + } + + expectThrows { + eventBus.register(object : Listener { + @EventHandler fun onFirstEvent(event: FirstEvent, coolParam: String) {} + }) + } + } + + it("calls handlers in the right order, sorted by priority") { + val order = mutableListOf() + + eventBus.register(object : Listener { + @EventHandler(EventPriority.HIGHEST) fun onFirstEventHighest(event: FirstEvent) { order.add(EventPriority.HIGHEST) } + @EventHandler(EventPriority.LOW) fun onFirstEventLow(event: FirstEvent) { order.add(EventPriority.LOW) } + @EventHandler(EventPriority.HIGHER) fun onFirstEventHigher(event: FirstEvent) { order.add(EventPriority.HIGHER) } + @EventHandler(EventPriority.LOWEST) fun onFirstEventLowest(event: FirstEvent) { order.add(EventPriority.LOWEST) } + @EventHandler(EventPriority.MONITOR) fun onFirstEventMonitor(event: FirstEvent) { order.add(EventPriority.MONITOR) } + @EventHandler(EventPriority.LOWER) fun onFirstEventLower(event: FirstEvent) { order.add(EventPriority.LOWER) } + @EventHandler(EventPriority.NORMAL) fun onFirstEventNormal(event: FirstEvent) { order.add(EventPriority.NORMAL) } + @EventHandler(EventPriority.HIGH) fun onFirstEventHigh(event: FirstEvent) { order.add(EventPriority.HIGH) } + }) + + eventBus.emit(FirstEvent()) + + expectThat(order).isSorted(Comparator.comparing { it.ordinal }) + } + } +}) diff --git a/blokk-server/build.gradle.kts b/blokk-server/build.gradle.kts new file mode 100644 index 0000000..ee7175c --- /dev/null +++ b/blokk-server/build.gradle.kts @@ -0,0 +1,37 @@ + + +plugins { + kotlin("jvm") + id("com.github.johnrengelman.shadow") version "6.0.0" +} + +group = rootProject.group +version = rootProject.version + +repositories { + mavenCentral() +} + +dependencies { + implementation(project(":blokk-api")) + implementation(kotlin("stdlib-jdk8")) + implementation("io.netty:netty-all:4.1.50.Final") + implementation("org.slf4j:slf4j-api:1.7.30") + implementation("ch.qos.logback:logback-classic:1.2.3") + testImplementation(kotlin("test-junit5")) +} + +tasks { + compileKotlin { + kotlinOptions.jvmTarget = "1.8" + } + + shadowJar { + archiveClassifier.set(null as String?) + + manifest { + this.attributes("Main-Class" to "space.blokk.BlokkServer") + this.attributes("Implementation-Version" to project.version) + } + } +} diff --git a/blokk-server/src/main/kotlin/space/blokk/BlokkServer.kt b/blokk-server/src/main/kotlin/space/blokk/BlokkServer.kt new file mode 100644 index 0000000..31a70ab --- /dev/null +++ b/blokk-server/src/main/kotlin/space/blokk/BlokkServer.kt @@ -0,0 +1,33 @@ +package space.blokk + +import space.blokk.net.BlokkSocketServer +import space.blokk.utils.Logger + +class BlokkServer internal constructor(): Server { + override val logger = Logger("BlokkServer") + val host = "0.0.0.0" + val port = 25565 + + init { + instance = this + Blokk.server = this + } + + fun start() { + logger info "Starting BlokkServer (${if (VERSION == "development") VERSION else "v$VERSION"})" + val blokkSocketServer = BlokkSocketServer(this) + blokkSocketServer.bind() + logger info "Listening on $host:$port" + } + + companion object { + lateinit var instance: BlokkServer; private set + val VERSION = BlokkServer::class.java.`package`.implementationVersion ?: "development" + + @JvmStatic + fun main(args: Array) { + val server = BlokkServer() + server.start() + } + } +} diff --git a/blokk-server/src/main/kotlin/space/blokk/net/BlokkChannelInitializer.kt b/blokk-server/src/main/kotlin/space/blokk/net/BlokkChannelInitializer.kt new file mode 100644 index 0000000..195cebb --- /dev/null +++ b/blokk-server/src/main/kotlin/space/blokk/net/BlokkChannelInitializer.kt @@ -0,0 +1,26 @@ +package space.blokk.net + +import io.netty.channel.Channel +import io.netty.channel.ChannelException +import io.netty.channel.ChannelInitializer +import io.netty.channel.ChannelOption +import io.netty.handler.timeout.IdleStateHandler +import space.blokk.BlokkServer + +class BlokkChannelInitializer(private val blokkSocketServer: BlokkSocketServer): ChannelInitializer() { + private val logger = BlokkServer.instance.logger + + override fun initChannel(ch: Channel) { + try { + ch.config().setOption(ChannelOption.IP_TOS, 0x18) + } catch (e: ChannelException) { + logger warn "Your OS does not support IP type of service" + } + + ch.pipeline() + .addLast(IdleStateHandler(20, 15, 0)) + .addLast(FramingCodec()) + .addLast(PacketCodec(blokkSocketServer)) + .addLast(PacketMessageHandler()) + } +} diff --git a/blokk-server/src/main/kotlin/space/blokk/net/BlokkSession.kt b/blokk-server/src/main/kotlin/space/blokk/net/BlokkSession.kt new file mode 100644 index 0000000..09342ee --- /dev/null +++ b/blokk-server/src/main/kotlin/space/blokk/net/BlokkSession.kt @@ -0,0 +1,27 @@ +package space.blokk.net + +import io.netty.channel.Channel +import space.blokk.Blokk +import space.blokk.events.EventBus +import space.blokk.net.protocols.ClientboundPacket +import space.blokk.net.protocols.Protocol +import space.blokk.net.protocols.handshaking.HandshakingProtocol +import java.net.InetAddress +import java.net.InetSocketAddress + +class BlokkSession(private val channel: Channel) : Session { + override var currentProtocol: Protocol = HandshakingProtocol + override val address: InetAddress = (channel.remoteAddress() as InetSocketAddress).address + + override val eventsManager = EventBus(SessionEvent::class) + + override fun send(packet: ClientboundPacket) { + Blokk.server.logger.debug { "Sending packet: $packet" } + val cf = channel.writeAndFlush(PacketMessage(this, packet)) + cf.addListener { future -> + if (!future.isSuccess) { + Blokk.server.logger.error { "Packet send failed: ${future.cause()}" } + } + } + } +} diff --git a/blokk-server/src/main/kotlin/space/blokk/net/BlokkSocketServer.kt b/blokk-server/src/main/kotlin/space/blokk/net/BlokkSocketServer.kt new file mode 100644 index 0000000..825b289 --- /dev/null +++ b/blokk-server/src/main/kotlin/space/blokk/net/BlokkSocketServer.kt @@ -0,0 +1,49 @@ +package space.blokk.net + +import io.netty.bootstrap.ServerBootstrap +import io.netty.channel.ChannelOption +import io.netty.channel.epoll.Epoll +import io.netty.channel.epoll.EpollEventLoopGroup +import io.netty.channel.epoll.EpollServerSocketChannel +import io.netty.channel.kqueue.KQueue +import io.netty.channel.kqueue.KQueueEventLoopGroup +import io.netty.channel.kqueue.KQueueServerSocketChannel +import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.socket.nio.NioServerSocketChannel +import space.blokk.BlokkServer +import space.blokk.net.protocols.Protocols + +class BlokkSocketServer(private val blokkServer: BlokkServer) { + private val bossGroup = createEventLoopGroup() + private val workerGroup = createEventLoopGroup() + private val bootstrap: ServerBootstrap = ServerBootstrap() + .group(bossGroup, workerGroup) + .channel(when { + EPOLL_AVAILABLE -> EpollServerSocketChannel::class.java + KQUEUE_AVAILABLE -> KQueueServerSocketChannel::class.java + else -> NioServerSocketChannel::class.java + }) + .childOption(ChannelOption.SO_KEEPALIVE, true) + .childOption(ChannelOption.TCP_NODELAY, true) + .childHandler(BlokkChannelInitializer(this)) + + val sessions = mutableSetOf() + + init { + Protocols.validate() + } + + fun bind() { + bootstrap.bind(blokkServer.host, blokkServer.port) + } + + companion object { + private val EPOLL_AVAILABLE = Epoll.isAvailable() + private val KQUEUE_AVAILABLE = KQueue.isAvailable() + private fun createEventLoopGroup() = when { + EPOLL_AVAILABLE -> EpollEventLoopGroup() + KQUEUE_AVAILABLE -> KQueueEventLoopGroup() + else -> NioEventLoopGroup() + } + } +} diff --git a/blokk-server/src/main/kotlin/space/blokk/net/FramingCodec.kt b/blokk-server/src/main/kotlin/space/blokk/net/FramingCodec.kt new file mode 100644 index 0000000..cd0edfd --- /dev/null +++ b/blokk-server/src/main/kotlin/space/blokk/net/FramingCodec.kt @@ -0,0 +1,31 @@ +package space.blokk.net + +import io.netty.buffer.ByteBuf +import io.netty.channel.ChannelHandlerContext +import io.netty.handler.codec.ByteToMessageCodec + +class FramingCodec: ByteToMessageCodec() { + private var currentLength: Int? = null + + override fun encode(ctx: ChannelHandlerContext, msg: ByteBuf, out: ByteBuf) { + out.writeVarInt(msg.readableBytes()) + msg.readerIndex(0) + out.writeBytes(msg) + } + + override fun decode(ctx: ChannelHandlerContext, msg: ByteBuf, out: MutableList) { + var length = currentLength + if (length == null) { + if (msg.varIntReadable()) { + length = msg.readVarInt() + currentLength = length + } + else return + } + + if (msg.readableBytes() >= length) { + out.add(msg.readBytes(length)) + currentLength = null + } + } +} diff --git a/blokk-server/src/main/kotlin/space/blokk/net/PacketCodec.kt b/blokk-server/src/main/kotlin/space/blokk/net/PacketCodec.kt new file mode 100644 index 0000000..4306caf --- /dev/null +++ b/blokk-server/src/main/kotlin/space/blokk/net/PacketCodec.kt @@ -0,0 +1,39 @@ +package space.blokk.net + +import io.netty.buffer.ByteBuf +import io.netty.channel.ChannelHandlerContext +import io.netty.handler.codec.MessageToMessageCodec +import space.blokk.net.protocols.ClientboundPacket +import java.io.IOException + +class PacketCodec(private val blokkSocketServer: BlokkSocketServer): MessageToMessageCodec() { + private lateinit var session: BlokkSession + + override fun channelActive(ctx: ChannelHandlerContext) { + session = BlokkSession(ctx.channel()) + blokkSocketServer.sessions.add(session) + } + + + override fun channelInactive(ctx: ChannelHandlerContext) { + blokkSocketServer.sessions.remove(session) + } + + override fun encode(ctx: ChannelHandlerContext, msg: PacketMessage, out: MutableList) { + if (msg.packet !is ClientboundPacket) throw Error("Only clientbound packets can be sent") + val buffer = ctx.alloc().buffer() + buffer.writeVarInt(msg.packetCompanion.id) + msg.packet.encode(buffer) + out.add(buffer) + } + + override fun decode(ctx: ChannelHandlerContext, msg: ByteBuf, out: MutableList) { + val packetID = msg.readVarInt() + val data = msg.readBytes(msg.readableBytes()) + + val packetCompanion = session.currentProtocol.serverboundPacketsByID[packetID] + ?: throw IOException("Client sent a packet with an invalid ID: $packetID") + + out.add(PacketMessage(session, packetCompanion.decode(data))) + } +} diff --git a/blokk-server/src/main/kotlin/space/blokk/net/PacketMessage.kt b/blokk-server/src/main/kotlin/space/blokk/net/PacketMessage.kt new file mode 100644 index 0000000..ec8909f --- /dev/null +++ b/blokk-server/src/main/kotlin/space/blokk/net/PacketMessage.kt @@ -0,0 +1,11 @@ +package space.blokk.net + +import space.blokk.net.protocols.Packet + +data class PacketMessage(val session: BlokkSession, val packet: Packet) { + val packetCompanion by lazy { + session.currentProtocol.packetCompanionsByPacketType[packet::class] + ?: throw Exception("No packet companion found for this packet type. " + + "This can happen if the packet is not part of the current protocol.") + } +} diff --git a/blokk-server/src/main/kotlin/space/blokk/net/PacketMessageHandler.kt b/blokk-server/src/main/kotlin/space/blokk/net/PacketMessageHandler.kt new file mode 100644 index 0000000..bf37b86 --- /dev/null +++ b/blokk-server/src/main/kotlin/space/blokk/net/PacketMessageHandler.kt @@ -0,0 +1,15 @@ +package space.blokk.net + +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import space.blokk.Blokk +import space.blokk.net.protocols.ServerboundPacket + +class PacketMessageHandler: SimpleChannelInboundHandler() { + override fun channelRead0(ctx: ChannelHandlerContext, msg: PacketMessage) { + if (msg.packet !is ServerboundPacket) throw Error("Only serverbound packets can be handled") + Blokk.server.logger.debug { "Packet received: ${msg.packet}" } + msg.packet.handle(msg.session) + // TODO: Disconnect when invalid data is received + } +} diff --git a/blokk-server/src/main/resources/logback.xml b/blokk-server/src/main/resources/logback.xml new file mode 100644 index 0000000..c5669c7 --- /dev/null +++ b/blokk-server/src/main/resources/logback.xml @@ -0,0 +1,12 @@ + + + + %d{HH:mm:ss.SSS} %thread %-5level [%logger{36}] %msg%n + + + + + + + + diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..ae367ff --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,24 @@ +plugins { + kotlin("jvm") version "1.3.71" +} + +group = "space.blokk" +version = "0.0.1" + +repositories { + mavenCentral() +} + +dependencies { + implementation(kotlin("stdlib-jdk8")) +} + +tasks { + compileKotlin { + kotlinOptions.jvmTarget = "1.8" + } + + compileTestKotlin { + kotlinOptions.jvmTarget = "1.8" + } +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..7fc6f1f --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +kotlin.code.style=official diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..490fda8 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..a4b4429 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..2fe81a7 --- /dev/null +++ b/gradlew @@ -0,0 +1,183 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..62bd9b9 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,103 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..87c7bae --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,12 @@ +pluginManagement { + repositories { + mavenCentral() + gradlePluginPortal() + } + +} + +rootProject.name = "blokk" + +include(":blokk-api") +include(":blokk-server")