Initial commit
This commit is contained in:
commit
cdfc11344d
48 changed files with 1306 additions and 0 deletions
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
# Project exclude paths
|
||||
/.gradle/
|
||||
/blokk-api/build/
|
||||
/blokk-server/build/
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -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.
|
41
blokk-api/build.gradle.kts
Normal file
41
blokk-api/build.gradle.kts
Normal file
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
5
blokk-api/src/main/kotlin/space/blokk/Blokk.kt
Normal file
5
blokk-api/src/main/kotlin/space/blokk/Blokk.kt
Normal file
|
@ -0,0 +1,5 @@
|
|||
package space.blokk
|
||||
|
||||
object Blokk {
|
||||
lateinit var server: Server
|
||||
}
|
7
blokk-api/src/main/kotlin/space/blokk/Server.kt
Normal file
7
blokk-api/src/main/kotlin/space/blokk/Server.kt
Normal file
|
@ -0,0 +1,7 @@
|
|||
package space.blokk
|
||||
|
||||
import space.blokk.utils.Logger
|
||||
|
||||
interface Server {
|
||||
val logger: Logger
|
||||
}
|
24
blokk-api/src/main/kotlin/space/blokk/chat/ChatComponent.kt
Normal file
24
blokk-api/src/main/kotlin/space/blokk/chat/ChatComponent.kt
Normal file
|
@ -0,0 +1,24 @@
|
|||
package space.blokk.chat
|
||||
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
sealed class ChatComponent {
|
||||
abstract val extra: ChatComponent?
|
||||
|
||||
fun getExtraTypes(): List<KClass<out ChatComponent>> {
|
||||
val types = mutableListOf<KClass<out ChatComponent>>()
|
||||
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)
|
||||
}
|
||||
}
|
33
blokk-api/src/main/kotlin/space/blokk/chat/FormattingCode.kt
Normal file
33
blokk-api/src/main/kotlin/space/blokk/chat/FormattingCode.kt
Normal file
|
@ -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"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package space.blokk.events
|
||||
|
||||
interface Cancellable {
|
||||
var isCancelled: Boolean
|
||||
}
|
4
blokk-api/src/main/kotlin/space/blokk/events/Event.kt
Normal file
4
blokk-api/src/main/kotlin/space/blokk/events/Event.kt
Normal file
|
@ -0,0 +1,4 @@
|
|||
package space.blokk.events
|
||||
|
||||
abstract class Event {
|
||||
}
|
51
blokk-api/src/main/kotlin/space/blokk/events/EventBus.kt
Normal file
51
blokk-api/src/main/kotlin/space/blokk/events/EventBus.kt
Normal file
|
@ -0,0 +1,51 @@
|
|||
package space.blokk.events
|
||||
|
||||
import space.blokk.plugins.Plugin
|
||||
import java.lang.reflect.Method
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
class EventBus<EventT: Event>(eventType: KClass<EventT>) {
|
||||
private val eventType: Class<EventT> = eventType.java
|
||||
|
||||
/**
|
||||
* All event handlers, sorted by their priority and the order in which they were inserted
|
||||
*/
|
||||
private val handlers = mutableListOf<Handler>()
|
||||
|
||||
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<out Event>, 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<out Event>,
|
||||
val listener: Listener,
|
||||
val fn: Method,
|
||||
val plugin: Plugin?,
|
||||
val priority: EventPriority
|
||||
)
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
package space.blokk.events
|
||||
|
||||
annotation class EventHandler(val priority: EventPriority = EventPriority.NORMAL)
|
|
@ -0,0 +1,12 @@
|
|||
package space.blokk.events
|
||||
|
||||
enum class EventPriority {
|
||||
LOWEST,
|
||||
LOW,
|
||||
LOWER,
|
||||
NORMAL,
|
||||
HIGHER,
|
||||
HIGH,
|
||||
HIGHEST,
|
||||
MONITOR
|
||||
}
|
3
blokk-api/src/main/kotlin/space/blokk/events/Listener.kt
Normal file
3
blokk-api/src/main/kotlin/space/blokk/events/Listener.kt
Normal file
|
@ -0,0 +1,3 @@
|
|||
package space.blokk.events
|
||||
|
||||
interface Listener
|
|
@ -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)
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package space.blokk.net
|
||||
|
||||
import space.blokk.events.Cancellable
|
||||
|
||||
open class PacketReceivedEvent: SessionEvent(), Cancellable {
|
||||
override var isCancelled = false
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package space.blokk.net
|
||||
|
||||
import space.blokk.net.protocols.ClientboundPacket
|
||||
|
||||
class PacketWithResponseReceivedEvent<ResponseT: ClientboundPacket>(
|
||||
var response: ResponseT
|
||||
): PacketReceivedEvent()
|
18
blokk-api/src/main/kotlin/space/blokk/net/Session.kt
Normal file
18
blokk-api/src/main/kotlin/space/blokk/net/Session.kt
Normal file
|
@ -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<SessionEvent>
|
||||
}
|
||||
|
||||
open class SessionEvent: Event()
|
|
@ -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<T: Packet>(val id: Int, val packetType: KClass<T>)
|
||||
|
||||
abstract class ServerboundPacketCompanion<T: ServerboundPacket>(
|
||||
id: Int,
|
||||
packetType: KClass<T>
|
||||
): PacketCompanion<T>(id, packetType) {
|
||||
abstract fun decode(msg: ByteBuf): T
|
||||
}
|
||||
|
||||
abstract class ClientboundPacketCompanion<T: ClientboundPacket>(
|
||||
id: Int,
|
||||
packetType: KClass<T>
|
||||
): PacketCompanion<T>(id, packetType)
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package space.blokk.net.protocols
|
||||
|
||||
abstract class Protocol(packets: Set<PacketCompanion<*>>) {
|
||||
val serverboundPackets = packets.filterIsInstance<ServerboundPacketCompanion<*>>()
|
||||
val serverboundPacketsByID = serverboundPackets.mapToIDMap()
|
||||
val clientboundPackets = packets.filterIsInstance<ClientboundPacketCompanion<*>>()
|
||||
val clientboundPacketsById = clientboundPackets.mapToIDMap()
|
||||
val packetCompanionsByPacketType = packets.map { it.packetType to it }.toMap()
|
||||
|
||||
private fun <T: PacketCompanion<*>> Iterable<T>.mapToIDMap() = map { it.id to it }.toMap()
|
||||
|
||||
fun validate() {
|
||||
ensureDistinctIDs(serverboundPackets, "serverbound")
|
||||
ensureDistinctIDs(clientboundPackets, "clientbound")
|
||||
}
|
||||
|
||||
private fun ensureDistinctIDs(packets: Iterable<PacketCompanion<*>>, 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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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<HandshakePacket>(0x00, HandshakePacket::class) {
|
||||
override fun decode(msg: ByteBuf): HandshakePacket {
|
||||
return HandshakePacket(
|
||||
protocolVersion = msg.readVarInt(),
|
||||
serverAddress = msg.readString(),
|
||||
serverPort = msg.readUnsignedShort(),
|
||||
login = msg.readVarInt() == 2
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package space.blokk.net.protocols.handshaking
|
||||
|
||||
import space.blokk.net.protocols.Protocol
|
||||
|
||||
object HandshakingProtocol : Protocol(setOf(HandshakePacket))
|
|
@ -0,0 +1,6 @@
|
|||
package space.blokk.net.protocols.login
|
||||
|
||||
import space.blokk.net.protocols.Protocol
|
||||
|
||||
object LoginProtocol: Protocol(setOf()) {
|
||||
}
|
|
@ -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<PingPacket>(0x01, PingPacket::class) {
|
||||
override fun decode(msg: ByteBuf): PingPacket {
|
||||
return PingPacket(msg.readLong())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<PongPacket>(0x01, PongPacket::class)
|
||||
}
|
|
@ -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<RequestPacket>(0x00, RequestPacket::class) {
|
||||
override fun decode(msg: ByteBuf): RequestPacket = RequestPacket()
|
||||
}
|
||||
}
|
|
@ -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<SampleEntry>) {
|
||||
data class SampleEntry(val name: String, val id: String)
|
||||
}
|
||||
|
||||
companion object : ClientboundPacketCompanion<ResponsePacket>(0x00, ResponsePacket::class) {
|
||||
private val gson = GsonBuilder().disableHtmlEscaping().create()!!
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package space.blokk.net.protocols.status
|
||||
|
||||
import space.blokk.net.protocols.Protocol
|
||||
|
||||
object StatusProtocol: Protocol(setOf(RequestPacket, ResponsePacket, PingPacket, PongPacket))
|
11
blokk-api/src/main/kotlin/space/blokk/plugins/Plugin.kt
Normal file
11
blokk-api/src/main/kotlin/space/blokk/plugins/Plugin.kt
Normal file
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
33
blokk-api/src/main/kotlin/space/blokk/utils/Logger.kt
Normal file
33
blokk-api/src/main/kotlin/space/blokk/utils/Logger.kt
Normal file
|
@ -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())
|
||||
}
|
||||
}
|
110
blokk-api/src/test/kotlin/EventBusTest.kt
Normal file
110
blokk-api/src/test/kotlin/EventBusTest.kt
Normal file
|
@ -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.InvalidEventHandlerException> {
|
||||
eventBus.register(object : Listener {
|
||||
@EventHandler fun onEvent(coolParam: String) {}
|
||||
})
|
||||
}
|
||||
|
||||
expectThrows<EventBus.InvalidEventHandlerException> {
|
||||
eventBus.register(object : Listener {
|
||||
@EventHandler fun onFirstEvent(coolParam: String, event: FirstEvent) {}
|
||||
})
|
||||
}
|
||||
|
||||
expectThrows<EventBus.InvalidEventHandlerException> {
|
||||
eventBus.register(object : Listener {
|
||||
@EventHandler fun onFirstEvent(event: FirstEvent, coolParam: String) {}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
it("calls handlers in the right order, sorted by priority") {
|
||||
val order = mutableListOf<EventPriority>()
|
||||
|
||||
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<EventPriority, Int> { it.ordinal })
|
||||
}
|
||||
}
|
||||
})
|
37
blokk-server/build.gradle.kts
Normal file
37
blokk-server/build.gradle.kts
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
33
blokk-server/src/main/kotlin/space/blokk/BlokkServer.kt
Normal file
33
blokk-server/src/main/kotlin/space/blokk/BlokkServer.kt
Normal file
|
@ -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<String>) {
|
||||
val server = BlokkServer()
|
||||
server.start()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<Channel>() {
|
||||
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())
|
||||
}
|
||||
}
|
27
blokk-server/src/main/kotlin/space/blokk/net/BlokkSession.kt
Normal file
27
blokk-server/src/main/kotlin/space/blokk/net/BlokkSession.kt
Normal file
|
@ -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()}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<BlokkSession>()
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
31
blokk-server/src/main/kotlin/space/blokk/net/FramingCodec.kt
Normal file
31
blokk-server/src/main/kotlin/space/blokk/net/FramingCodec.kt
Normal file
|
@ -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<ByteBuf>() {
|
||||
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<Any>) {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
39
blokk-server/src/main/kotlin/space/blokk/net/PacketCodec.kt
Normal file
39
blokk-server/src/main/kotlin/space/blokk/net/PacketCodec.kt
Normal file
|
@ -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<ByteBuf, PacketMessage>() {
|
||||
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<Any>) {
|
||||
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<Any>) {
|
||||
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)))
|
||||
}
|
||||
}
|
|
@ -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.")
|
||||
}
|
||||
}
|
|
@ -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<PacketMessage>() {
|
||||
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
|
||||
}
|
||||
}
|
12
blokk-server/src/main/resources/logback.xml
Normal file
12
blokk-server/src/main/resources/logback.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} %thread %-5level [%logger{36}] %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
<root level="debug">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
|
||||
<logger name="io.netty" level="WARN"/>
|
||||
</configuration>
|
24
build.gradle.kts
Normal file
24
build.gradle.kts
Normal file
|
@ -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"
|
||||
}
|
||||
}
|
1
gradle.properties
Normal file
1
gradle.properties
Normal file
|
@ -0,0 +1 @@
|
|||
kotlin.code.style=official
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
@ -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
|
183
gradlew
vendored
Executable file
183
gradlew
vendored
Executable file
|
@ -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" "$@"
|
103
gradlew.bat
vendored
Normal file
103
gradlew.bat
vendored
Normal file
|
@ -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
|
12
settings.gradle.kts
Normal file
12
settings.gradle.kts
Normal file
|
@ -0,0 +1,12 @@
|
|||
pluginManagement {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
rootProject.name = "blokk"
|
||||
|
||||
include(":blokk-api")
|
||||
include(":blokk-server")
|
Reference in a new issue