4.0.0 with bad build system

This commit is contained in:
Skye 2023-07-20 21:32:44 +09:00
parent ceb0bedb79
commit 66247df95d
Signed by: me
GPG key ID: 0104BC05F41B77B8
12 changed files with 558 additions and 338 deletions

View file

@ -4,9 +4,6 @@
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="delegatedBuild" value="true" />
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>

View file

@ -3,5 +3,5 @@ org.gradle.jvmargs=-Xmx2G
mod.name=e4mc
mod.id=e4mc_minecraft
mod.version=3.2.0
mod.version=4.0.0
mod.group=vg.skye

View file

@ -10,7 +10,6 @@ pluginManagement {
maven("https://repo.essential.gg/repository/maven-public")
maven("https://server.bbkr.space/artifactory/libs-release/")
maven("https://jitpack.io/")
// Snapshots
maven("https://maven.deftu.xyz/snapshots")
mavenLocal()
@ -25,7 +24,7 @@ pluginManagement {
kotlin("jvm") version(kotlin)
kotlin("plugin.serialization") version(kotlin)
val epgt = "1.15.1"
val epgt = "1.17.1"
id("xyz.deftu.gradle.multiversion-root") version(epgt)
}
}

View file

@ -5,7 +5,6 @@ import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ServerChannel;
import io.netty.channel.local.LocalAddress;
import io.netty.channel.local.LocalServerChannel;
import io.netty.channel.socket.ServerSocketChannel;
import net.minecraft.server.ServerNetworkIo;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@ -15,7 +14,7 @@ import org.spongepowered.asm.mixin.injection.ModifyArg;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import vg.skye.e4mc_minecraft.E4mcClient;
import vg.skye.e4mc_minecraft.E4mcRelayHandler;
import vg.skye.e4mc_minecraft.QuiclimeHandler;
import javax.annotation.Nullable;
import java.io.IOException;
@ -40,9 +39,9 @@ public abstract class ServerNetworkIoMixin {
initializingE4mc.set(false);
}
} else {
E4mcRelayHandler handler = new E4mcRelayHandler();
QuiclimeHandler handler = new QuiclimeHandler();
E4mcClient.HANDLER = handler;
handler.connect();
handler.startAsync();
}
}
@ -59,7 +58,7 @@ public abstract class ServerNetworkIoMixin {
@Inject(method = "stop", at = @At("HEAD"))
private void stop(CallbackInfo ci) {
if (E4mcClient.HANDLER != null) {
E4mcClient.HANDLER.close();
E4mcClient.HANDLER.stop();
}
}

View file

@ -0,0 +1,129 @@
package vg.skye.e4mc_minecraft
//#if FABRIC==1
import net.fabricmc.api.EnvType
import net.fabricmc.loader.api.FabricLoader
//#else
//$$ import net.minecraftforge.fml.loading.FMLLoader
//#endif
import net.minecraft.client.MinecraftClient
import net.minecraft.text.ClickEvent
import net.minecraft.text.HoverEvent
import net.minecraft.text.Text
//#if MC>=11900
import net.minecraft.util.Formatting
//#elseif FABRIC==1
//$$ import net.minecraft.text.TranslatableText
//$$ import net.minecraft.text.LiteralText
//$$ import net.minecraft.util.Formatting
//#else
//$$ import net.minecraft.network.chat.TranslatableComponent
//$$ import net.minecraft.network.chat.TextComponent
//$$ import net.minecraft.ChatFormatting
//#endif
object ChatHelper {
//#if FABRIC==1
val isClient = FabricLoader.getInstance().environmentType.equals(EnvType.CLIENT)
val isServer = FabricLoader.getInstance().environmentType.equals(EnvType.SERVER)
//#else
//$$ val isClient = FMLLoader.getDist().isClient
//$$ val isServer = FMLLoader.getDist().isDedicatedServer
//#endif
fun sendLiteral(msg: String) {
alertUser(createLiteralMessage("[e4mc] $msg"))
}
fun sendError() {
if (E4mcClient.HANDLER?.state == QuiclimeHandlerState.STARTED) {
E4mcClient.HANDLER?.state = QuiclimeHandlerState.UNHEALTHY
}
alertUser(createTranslatableMessage("text.e4mc_minecraft.error"))
}
fun sendDomainAssignment(domain: String) {
if (isServer) {
E4mcClient.LOGGER.warn("e4mc running on Dedicated Server; This works, but isn't recommended as e4mc is designed for short-lived LAN servers")
}
E4mcClient.LOGGER.info("Domain assigned: $domain")
alertUser(createDomainAssignedMessage(domain))
}
private fun alertUser(message: Text) {
if (isClient) {
MinecraftClient.getInstance().inGameHud.chatHud.addMessage(
message
)
}
}
private fun createLiteralMessage(message: String): Text {
//#if MC>=11900
return Text.literal(message)
//#elseif FABRIC==1
//$$ return LiteralText(message)
//#else
//$$ return TextComponent(message)
//#endif
}
fun createTranslatableMessage(key: String, vararg objects: Any?): Text {
//#if MC>=11900
return Text.translatable(key, *objects)
//#elseif FABRIC==1
//$$ return TranslatableText(key, *objects)
//#else
//$$ return TranslatableComponent(key, *objects)
//#endif
}
private fun createDomainAssignedMessage(domain: String): Text {
//#if MC>=11900
return Text.translatable(
"text.e4mc_minecraft.domainAssigned",
Text.literal(domain).styled {
it
.withClickEvent(ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, domain))
.withColor(Formatting.GREEN)
.withHoverEvent(HoverEvent(HoverEvent.Action.SHOW_TEXT, Text.translatable("chat.copy.click")))
}
).append(
Text.translatable("text.e4mc_minecraft.clickToStop").styled {
it
.withClickEvent(ClickEvent(ClickEvent.Action.RUN_COMMAND, "/e4mc stop"))
.withColor(Formatting.GRAY)
}
)
//#elseif FABRIC==1
//$$ return TranslatableText("text.e4mc_minecraft.domainAssigned", LiteralText(domain).styled {
//$$ return@styled it
//$$ .withClickEvent(ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, domain))
//$$ .withColor(Formatting.GREEN)
//$$ .withHoverEvent(HoverEvent(HoverEvent.Action.SHOW_TEXT, TranslatableText("chat.copy.click")))
//$$ }).append(
//$$ TranslatableText("text.e4mc_minecraft.clickToStop").styled {
//$$ return@styled it
//$$ .withClickEvent(ClickEvent(ClickEvent.Action.RUN_COMMAND, "/e4mc stop"))
//$$ .withColor(Formatting.GRAY)
//$$ }
//$$ )
//#else
//$$ return TranslatableComponent("text.e4mc_minecraft.domainAssigned", TextComponent(domain).withStyle {
//$$ return@withStyle it
//$$ .withClickEvent(ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, domain))
//$$ .withColor(ChatFormatting.GREEN)
//$$ .withHoverEvent(HoverEvent(HoverEvent.Action.SHOW_TEXT, TranslatableComponent("chat.copy.click")))
//$$ }).append(
//$$ TranslatableComponent("text.e4mc_minecraft.clickToStop").withStyle {
//$$ return@withStyle it
//$$ .withClickEvent(ClickEvent(ClickEvent.Action.RUN_COMMAND, "/e4mc stop"))
//$$ .withColor(ChatFormatting.GRAY)
//$$ }
//$$ )
//#endif
}
}

View file

@ -0,0 +1,61 @@
package vg.skye.e4mc_minecraft
import com.mojang.brigadier.CommandDispatcher
import net.minecraft.server.command.CommandManager
import net.minecraft.server.command.ServerCommandSource
import net.minecraft.server.network.ServerPlayerEntity
import net.minecraft.text.Text
object CommandsHelper {
private fun getPlayerFromSource(src: ServerCommandSource): ServerPlayerEntity? {
return try {
src.playerOrThrow
} catch (e: Exception) {
null
}
}
fun registerCommandWithDispatcher(dispatcher: CommandDispatcher<ServerCommandSource>) {
dispatcher.register(
CommandManager.literal("e4mc")
.requires { src ->
if (src.server.isDedicated) {
src.hasPermissionLevel(4)
} else {
src.server.isHost((getPlayerFromSource(src) ?: return@requires false).gameProfile)
}
}
.then(
CommandManager.literal("stop")
.executes { context ->
if ((E4mcClient.HANDLER != null) && (E4mcClient.HANDLER?.state != QuiclimeHandlerState.STOPPED)) {
E4mcClient.HANDLER!!.stop()
sendMessageToSource(ChatHelper.createTranslatableMessage("text.e4mc_minecraft.closeServer"), context.source)
} else {
sendErrorToSource(ChatHelper.createTranslatableMessage("text.e4mc_minecraft.serverAlreadyClosed"), context.source)
}
1
}
)
.then(CommandManager.literal("restart")
.executes {
if ((E4mcClient.HANDLER != null) && (E4mcClient.HANDLER?.state != QuiclimeHandlerState.STARTED)) {
E4mcClient.HANDLER?.stop()
val handler = QuiclimeHandler()
E4mcClient.HANDLER = handler
handler.startAsync()
}
1
}
)
)
}
private fun sendMessageToSource(text: Text, source: ServerCommandSource) {
source.sendFeedback(text, true)
}
private fun sendErrorToSource(text: Text, source: ServerCommandSource) {
source.sendError(text)
}
}

View file

@ -33,11 +33,11 @@ object E4mcClient : ModInitializer {
//#endif
const val NAME = "e4mc"
const val ID = "e4mc_minecraft"
const val VERSION = "3.2.0"
const val VERSION = "4.0.0"
@JvmField
val LOGGER: Logger = LoggerFactory.getLogger("e4mc")
@JvmField
var HANDLER: E4mcRelayHandler? = null
var HANDLER: QuiclimeHandler? = null
//#if FABRIC==1
override fun onInitialize() {
@ -46,63 +46,14 @@ object E4mcClient : ModInitializer {
//#else
//$$ CommandRegistrationCallback.EVENT.register { dispatcher, _ ->
//#endif
dispatcher.register(literal("e4mc")
.then(
literal("stop")
.requires { src ->
if (src.server.isDedicated) {
src.hasPermissionLevel(4)
} else {
src.server.isHost((src.player ?: return@requires false).gameProfile)
}
}
.executes { context ->
if (HANDLER != null) {
HANDLER!!.close()
HANDLER = null
//#if MC>=11904
context.source.sendMessage(Text.translatable("text.e4mc_minecraft.closeServer"))
//#else
//$$ context.source.sendFeedback(TranslatableText("text.e4mc_minecraft.closeServer"), false)
//#endif
} else {
//#if MC>=11904
context.source.sendMessage(Text.translatable("text.e4mc_minecraft.serverAlreadyClosed"))
//#else
//$$ context.source.sendFeedback(TranslatableText("text.e4mc_minecraft.serverAlreadyClosed"), false)
//#endif
}
1
}
))
CommandsHelper.registerCommandWithDispatcher(dispatcher)
}
}
//#else
//$$ @SubscribeEvent
//$$ fun onRegisterCommandEvent(event: RegisterCommandsEvent) {
//$$ val commandDispatcher = event.getDispatcher()
//$$ commandDispatcher.register(literal("e4mc")
//$$ .then(
//$$ literal("stop")
//$$ .executes { context ->
//$$ if (HANDLER != null) {
//$$ HANDLER!!.close()
//$$ HANDLER = null
//$$ //#if MC>=11904
//$$ context.source.sendSuccess(Component.translatable("text.e4mc_minecraft.closeServer"), false)
//$$ //#else
//$$ //$$ context.source.sendSuccess(TranslatableComponent("text.e4mc_minecraft.closeServer"), false)
//$$ //#endif
//$$ } else {
//$$ //#if MC>=11904
//$$ context.source.sendFailure(Component.translatable("text.e4mc_minecraft.serverAlreadyClosed"))
//$$ //#else
//$$ //$$ context.source.sendFailure(TranslatableComponent("text.e4mc_minecraft.serverAlreadyClosed"))
//$$ //#endif
//$$ }
//$$ 1
//$$ }
//$$ ))
//$$ val dispatcher = event.getDispatcher()
//$$ CommandsHelper.registerCommandWithDispatcher(dispatcher)
//$$ }
//#endif
}

View file

@ -1,255 +0,0 @@
package vg.skye.e4mc_minecraft
import com.google.gson.Gson
import com.google.gson.JsonObject
import io.netty.bootstrap.Bootstrap
import io.netty.buffer.ByteBuf
import io.netty.channel.*
import io.netty.channel.local.LocalAddress
import io.netty.channel.local.LocalChannel
import io.netty.channel.nio.NioEventLoopGroup
//#if FABRIC==1
import net.fabricmc.api.EnvType
import net.fabricmc.loader.api.FabricLoader
//#else
//$$ import net.minecraftforge.fml.loading.FMLLoader
//#endif
import net.minecraft.client.MinecraftClient
import net.minecraft.text.ClickEvent
import net.minecraft.text.HoverEvent
import net.minecraft.text.Text
//#if MC>=11900
import net.minecraft.util.Formatting
//#elseif FABRIC==1
//$$ import net.minecraft.text.TranslatableText
//$$ import net.minecraft.text.LiteralText
//$$ import net.minecraft.util.Formatting
//#else
//$$ import net.minecraft.network.chat.TranslatableComponent
//$$ import net.minecraft.network.chat.TextComponent
//$$ import net.minecraft.ChatFormatting
//#endif
import org.java_websocket.client.WebSocketClient
import org.java_websocket.handshake.ServerHandshake
import java.lang.Exception
import java.net.InetSocketAddress
import java.net.URI
import java.nio.ByteBuffer
import java.util.concurrent.ArrayBlockingQueue
data class DomainAssignedMessage(val DomainAssigned: String)
data class ChannelOpenMessage(val ChannelOpen: List<Any>)
data class ChannelClosedMessage(val ChannelClosed: Number)
class E4mcRelayHandler: WebSocketClient(URI(System.getProperty("vg.skye.e4mc_minecraft.ingress_uri", "wss://ingress.e4mc.link"))) {
private val gson = Gson()
private val childChannels = mutableMapOf<Int, LocalChannel>()
private val messageQueue = mutableMapOf<Int, ArrayBlockingQueue<ByteBuffer>>()
private val eventLoopGroup = NioEventLoopGroup()
override fun onOpen(handshakedata: ServerHandshake?) {
// not much to do here
}
override fun onMessage(message: String?) {
E4mcClient.LOGGER.info("WebSocket Text message: {}", message)
val json = gson.fromJson(message, JsonObject::class.java)
when {
json.has("DomainAssigned") -> handleDomainAssigned(json)
json.has("ChannelOpen") -> handleChannelOpen(json)
json.has("ChannelClosed") -> handleChannelClosed(json)
else -> E4mcClient.LOGGER.warn("Unhandled WebSocket Text message: $message")
}
}
override fun onMessage(bytes: ByteBuffer) {
val channelId = bytes.get()
val rest = bytes.slice()
val channel = childChannels[channelId.toInt()]
if (channel == null) {
if (messageQueue[channelId.toInt()] == null) {
E4mcClient.LOGGER.info("Creating queue for channel: {}", channelId)
messageQueue[channelId.toInt()] = ArrayBlockingQueue(8)
}
messageQueue[channelId.toInt()]!!.add(rest)
} else {
val byteBuf = channel.alloc().buffer(rest.remaining())
byteBuf.writeBytes(rest)
channel.writeAndFlush(byteBuf).sync()
}
}
override fun onClose(code: Int, reason: String?, remote: Boolean) {
childChannels.forEach { (_, channel) -> channel.close() }
}
override fun onError(ex: java.lang.Exception) {
ex.printStackTrace()
//#if FABRIC==1
val isClient = FabricLoader.getInstance().environmentType.equals(EnvType.CLIENT)
//#else
//$$ val isClient = FMLLoader.getDist().isClient
//#endif
if (isClient) {
try {
MinecraftClient.getInstance().inGameHud.chatHud.addMessage(
//#if MC>=11900
Text.translatable("text.e4mc_minecraft.error")
//#elseif FABRIC==1
//$$ TranslatableText("text.e4mc_minecraft.error")
//#else
//$$ TranslatableComponent("text.e4mc_minecraft.error")
//#endif
)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
private fun handleDomainAssigned(json: JsonObject) {
val msg = gson.fromJson(json, DomainAssignedMessage::class.java)
//#if FABRIC==1
val isClient = FabricLoader.getInstance().environmentType.equals(EnvType.CLIENT)
val isServer = FabricLoader.getInstance().environmentType.equals(EnvType.SERVER)
//#else
//$$ val isClient = FMLLoader.getDist().isClient
//$$ val isServer = FMLLoader.getDist().isDedicatedServer
//#endif
if (isServer) {
E4mcClient.LOGGER.warn("e4mc running on Dedicated Server; This works, but isn't recommended as e4mc is designed for short-lived LAN servers")
}
E4mcClient.LOGGER.info("Domain assigned: ${msg.DomainAssigned}")
if (isClient) {
alertUser(msg.DomainAssigned)
}
}
private fun alertUser(domain: String) {
try {
MinecraftClient.getInstance().inGameHud.chatHud.addMessage(
createMessage(domain)
)
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun createMessage(domain: String): Text {
//#if MC>=11900
return Text.translatable(
"text.e4mc_minecraft.domainAssigned",
Text.literal(domain).styled {
it
.withClickEvent(ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, domain))
.withColor(Formatting.GREEN)
.withHoverEvent(HoverEvent(HoverEvent.Action.SHOW_TEXT, Text.translatable("chat.copy.click")))
}
).append(
Text.translatable("text.e4mc_minecraft.clickToStop").styled {
it
.withClickEvent(ClickEvent(ClickEvent.Action.RUN_COMMAND, "/e4mc stop"))
.withColor(Formatting.GRAY)
}
)
//#elseif FABRIC==1
//$$ return TranslatableText("text.e4mc_minecraft.domainAssigned", LiteralText(domain).styled {
//$$ return@styled it
//$$ .withClickEvent(ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, domain))
//$$ .withColor(Formatting.GREEN)
//$$ .withHoverEvent(HoverEvent(HoverEvent.Action.SHOW_TEXT, TranslatableText("chat.copy.click")))
//$$ }).append(
//$$ TranslatableText("text.e4mc_minecraft.clickToStop").styled {
//$$ return@styled it
//$$ .withClickEvent(ClickEvent(ClickEvent.Action.RUN_COMMAND, "/e4mc stop"))
//$$ .withColor(Formatting.GRAY)
//$$ }
//$$ )
//#else
//$$ return TranslatableComponent("text.e4mc_minecraft.domainAssigned", TextComponent(domain).withStyle {
//$$ return@withStyle it
//$$ .withClickEvent(ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, domain))
//$$ .withColor(ChatFormatting.GREEN)
//$$ .withHoverEvent(HoverEvent(HoverEvent.Action.SHOW_TEXT, TranslatableComponent("chat.copy.click")))
//$$ }).append(
//$$ TranslatableComponent("text.e4mc_minecraft.clickToStop").withStyle {
//$$ return@withStyle it
//$$ .withClickEvent(ClickEvent(ClickEvent.Action.RUN_COMMAND, "/e4mc stop"))
//$$ .withColor(ChatFormatting.GRAY)
//$$ }
//$$ )
//#endif
}
private fun handleChannelOpen(json: JsonObject) {
val msg = gson.fromJson(json, ChannelOpenMessage::class.java)
val channelId = msg.ChannelOpen[0] as Number
val clientInfo = msg.ChannelOpen[1] as String
E4mcClient.LOGGER.info("Channel opened: channelId=$channelId, clientInfo=$clientInfo")
val host = clientInfo.substringBeforeLast(':')
val port = clientInfo.substringAfterLast(':').toInt()
val addr = InetSocketAddress(host, port)
val self = this
Bootstrap()
.channel(LocalChannel::class.java)
.handler(object : ChannelInitializer<LocalChannel>() {
@Throws(java.lang.Exception::class)
override fun initChannel(ch: LocalChannel) {
ch.pipeline().addLast(ChildHandler(self, addr, channelId.toInt()))
}
})
.group(eventLoopGroup)
.connect(LocalAddress("e4mc-relay"))
}
private fun handleChannelClosed(json: JsonObject) {
val msg = gson.fromJson(json, ChannelClosedMessage::class.java)
val channelId = msg.ChannelClosed.toInt()
E4mcClient.LOGGER.info("Closing channel as requested: {}", channelId)
childChannels.remove(channelId)?.let {
it.pipeline().get(ChildHandler::class.java).isClosedFromServer = true
it.close()
E4mcClient.LOGGER.info("Channel closed: channelId=$channelId")
}
}
class ChildHandler(private val parent: E4mcRelayHandler, private val address: InetSocketAddress, private val channelId: Int): SimpleChannelInboundHandler<ByteBuf>() {
var isClosedFromServer = false
override fun channelActive(ctx: ChannelHandlerContext) {
// ctx.writeAndFlush(address)
if (parent.messageQueue[channelId] != null) {
parent.messageQueue[channelId]!!.forEach { buf ->
run {
E4mcClient.LOGGER.info("Handling queued buffer: {}", buf)
val byteBuf = ctx.alloc().buffer(buf.remaining())
byteBuf.writeBytes(buf)
ctx.writeAndFlush(byteBuf)
}
}
parent.messageQueue.remove(channelId)
}
parent.childChannels[channelId] = ctx.channel() as LocalChannel
}
override fun channelRead0(ctx: ChannelHandlerContext, msg: ByteBuf) {
val buf = ByteArray(msg.readableBytes() + 1)
buf[0] = channelId.toByte()
msg.readBytes(buf, 1, msg.readableBytes())
parent.send(buf)
}
override fun channelInactive(ctx: ChannelHandlerContext) {
E4mcClient.LOGGER.info("Channel closed: {}", channelId)
if (!isClosedFromServer) {
parent.send(parent.gson.toJson(ChannelClosedMessage(channelId)))
}
parent.childChannels.remove(channelId)
}
}
}

View file

@ -0,0 +1,320 @@
package vg.skye.e4mc_minecraft
import com.google.gson.Gson
import com.google.gson.JsonObject
import io.netty.bootstrap.Bootstrap
import io.netty.buffer.ByteBuf
import io.netty.buffer.Unpooled
import io.netty.channel.*
import io.netty.channel.ChannelHandler.Sharable
import io.netty.channel.local.LocalAddress
import io.netty.channel.local.LocalChannel
import io.netty.channel.nio.NioEventLoopGroup
import io.netty.channel.socket.ChannelInputShutdownReadComplete
import io.netty.channel.socket.nio.NioDatagramChannel
import io.netty.handler.codec.ByteToMessageCodec
import io.netty.incubator.codec.quic.*
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.net.Inet4Address
import java.net.InetAddress
import java.net.InetSocketAddress
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import java.util.concurrent.TimeUnit
import kotlin.concurrent.thread
interface QuiclimeControlMessage
data class RequestDomainAssignmentMessageServerbound(val kind: String = "request_domain_assignment"): QuiclimeControlMessage
data class DomainAssignmentCompleteMessageClientbound(val kind: String = "domain_assignment_complete", val domain: String): QuiclimeControlMessage
data class RequestMessageBroadcastMessageClientbound(val kind: String = "request_message_broadcast", val message: String): QuiclimeControlMessage
class QuiclimeControlMessageCodec : ByteToMessageCodec<QuiclimeControlMessage>() {
val gson = Gson()
override fun encode(ctx: ChannelHandlerContext, msg: QuiclimeControlMessage, out: ByteBuf) {
val json = gson.toJson(msg).toByteArray()
out.writeByte(json.size)
out.writeBytes(json)
}
override fun decode(ctx: ChannelHandlerContext, `in`: ByteBuf, out: MutableList<Any>) {
val size = `in`.getByte(`in`.readerIndex()).toInt()
if (`in`.readableBytes() >= size + 1) {
`in`.skipBytes(1)
val buf = ByteArray(size)
`in`.readBytes(buf)
val json = gson.fromJson(buf.decodeToString(), JsonObject::class.java)
out.add(gson.fromJson(json, when (json["kind"].asString) {
"domain_assignment_complete" -> DomainAssignmentCompleteMessageClientbound::class.java
"request_message_broadcast" -> RequestMessageBroadcastMessageClientbound::class.java
else -> throw Exception("Invalid message type")
}))
}
}
}
class QuiclimeToMinecraftHandler(private val toQuiclime: QuicStreamChannel) : ChannelInboundHandlerAdapter() {
override fun channelActive(ctx: ChannelHandlerContext) {
ctx.read()
}
override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {
toQuiclime.writeAndFlush(msg).addListener {
if (it.isSuccess) {
ctx.channel().read()
} else {
ChatHelper.sendError()
toQuiclime.close()
}
}
}
override fun channelInactive(ctx: ChannelHandlerContext) {
QuiclimeHandler.LOGGER.info("channel inactive(from MC): {} (MC: {})", toQuiclime, ctx.channel())
if (toQuiclime.isActive) {
toQuiclime.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE)
}
}
@Suppress("OVERRIDE_DEPRECATION")
override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable?) {
super.exceptionCaught(ctx, cause)
ChatHelper.sendError()
this.channelInactive(ctx)
}
}
class QuiclimeToQuiclimeHandler : ChannelInboundHandlerAdapter() {
private var toMinecraft: LocalChannel? = null
override fun channelActive(ctx: ChannelHandlerContext) {
QuiclimeHandler.LOGGER.info("channel active: {}", ctx.channel())
val fut = Bootstrap()
.group(ctx.channel().eventLoop())
.channel(LocalChannel::class.java)
.handler(QuiclimeToMinecraftHandler(ctx.channel() as QuicStreamChannel))
.option(ChannelOption.AUTO_READ, false)
.connect(LocalAddress("e4mc-relay"))
toMinecraft = fut.channel() as LocalChannel
fut.addListener {
if (it.isSuccess) {
ctx.channel().read()
} else {
ChatHelper.sendError()
ctx.channel().close()
}
}
}
override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {
if (toMinecraft?.isActive == true) {
toMinecraft!!.writeAndFlush(msg).addListener {
if (it.isSuccess) {
ctx.channel().read();
} else {
ChatHelper.sendError()
(it as ChannelFuture).channel().close();
}
}
}
}
override fun channelInactive(ctx: ChannelHandlerContext) {
QuiclimeHandler.LOGGER.info("channel inactive(from Quiclime): {} (MC: {})", ctx.channel(), toMinecraft)
if (toMinecraft?.isActive == true) {
toMinecraft!!.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE)
}
}
override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) {
if (evt === ChannelInputShutdownReadComplete.INSTANCE) {
this.channelInactive(ctx)
}
super.userEventTriggered(ctx, evt)
}
@Suppress("OVERRIDE_DEPRECATION")
override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable?) {
super.exceptionCaught(ctx, cause)
ChatHelper.sendError()
this.channelInactive(ctx)
}
}
data class BrokerResponse(val id: String, val host: String, val port: Int)
enum class QuiclimeHandlerState {
STARTING,
STARTED,
UNHEALTHY,
STOPPING,
STOPPED
}
class QuiclimeHandler {
private val group = NioEventLoopGroup()
private var datagramChannel: NioDatagramChannel? = null
private var quicChannel: QuicChannel? = null
var state: QuiclimeHandlerState = QuiclimeHandlerState.STARTING
companion object {
val LOGGER: Logger = LoggerFactory.getLogger("e4mc-quiclime")
fun startAsync() {
thread(start = true) {
E4mcClient.HANDLER = QuiclimeHandler()
}
}
}
fun startAsync() {
thread(start = true) {
start()
}
}
fun start() {
try {
val httpClient = HttpClient.newHttpClient()
val request = HttpRequest
.newBuilder(URI("https://broker.e4mc.link/getBestRelay"))
.header("Accept", "application/json")
.build()
LOGGER.info("req: {}", request)
val response = httpClient.send(request, HttpResponse.BodyHandlers.ofString())
LOGGER.info("resp: {}", response)
if (response.statusCode() != 200) {
throw Exception()
}
val relayInfo = Gson().fromJson(response.body(), BrokerResponse::class.java)
LOGGER.info("using relay {}", relayInfo.id)
val context: QuicSslContext = QuicSslContextBuilder
.forClient()
.applicationProtocols("quiclime")
.build()
val codec = QuicClientCodecBuilder()
.sslContext(context)
.sslEngineProvider {
context.newEngine(it.alloc(), relayInfo.host, relayInfo.port)
}
.initialMaxStreamsBidirectional(512)
.maxIdleTimeout(10, TimeUnit.SECONDS)
.initialMaxData(4611686018427387903)
.initialMaxStreamDataBidirectionalRemote(1250000)
.initialMaxStreamDataBidirectionalLocal(1250000)
.initialMaxStreamDataUnidirectional(1250000)
.build()
Bootstrap()
.group(group)
.channel(NioDatagramChannel::class.java)
.handler(codec)
.bind(0)
.addListener { datagramChannelFuture ->
if (!datagramChannelFuture.isSuccess) {
ChatHelper.sendError()
throw datagramChannelFuture.cause()
}
datagramChannel = (datagramChannelFuture as ChannelFuture).channel() as NioDatagramChannel
QuicChannel.newBootstrap(datagramChannel)
.streamHandler(
@Sharable
object : ChannelInitializer<QuicStreamChannel>() {
override fun initChannel(ch: QuicStreamChannel) {
ch.pipeline().addLast(QuiclimeToQuiclimeHandler())
}
}
)
.handler(object : ChannelInboundHandlerAdapter() {
@Suppress("OVERRIDE_DEPRECATION")
override fun exceptionCaught(ctx: ChannelHandlerContext?, cause: Throwable?) {
super.exceptionCaught(ctx, cause)
ChatHelper.sendError()
}
override fun channelInactive(ctx: ChannelHandlerContext) {
super.channelInactive(ctx)
state = QuiclimeHandlerState.STOPPED
}
})
.streamOption(ChannelOption.AUTO_READ, false)
.remoteAddress(InetSocketAddress(InetAddress.getByName(relayInfo.host), relayInfo.port))
.connect()
.addListener { quicChannelFuture ->
if (!quicChannelFuture.isSuccess) {
ChatHelper.sendError()
throw quicChannelFuture.cause()
}
quicChannel = quicChannelFuture.get() as QuicChannel
quicChannel!!.createStream(QuicStreamType.BIDIRECTIONAL,
object : ChannelInitializer<QuicStreamChannel>() {
override fun initChannel(ch: QuicStreamChannel) {
ch.pipeline().addLast(QuiclimeControlMessageCodec(), object : SimpleChannelInboundHandler<QuiclimeControlMessage>() {
override fun channelRead0(ctx: ChannelHandlerContext?, msg: QuiclimeControlMessage?) {
when (msg) {
is DomainAssignmentCompleteMessageClientbound -> {
state = QuiclimeHandlerState.STARTED
ChatHelper.sendDomainAssignment(msg.domain)
}
is RequestMessageBroadcastMessageClientbound -> {
ChatHelper.sendLiteral(msg.message)
}
}
}
})
}
}).addListener {
if (!it.isSuccess) {
ChatHelper.sendError()
throw it.cause()
}
val streamChannel = it.now as QuicStreamChannel
LOGGER.info("control channel open: {}", streamChannel)
streamChannel
.writeAndFlush(RequestDomainAssignmentMessageServerbound())
.addListener {
LOGGER.info("control channel write complete")
}
quicChannel!!.closeFuture().addListener {
datagramChannel?.close()
}
}
}
}
} catch (e: Exception) {
ChatHelper.sendError()
this.stop()
throw e
}
}
fun stop() {
state = QuiclimeHandlerState.STOPPING
if (quicChannel?.close()?.addListener {
if (datagramChannel?.close()?.addListener {
group.shutdownGracefully().addListener {
state = QuiclimeHandlerState.STOPPED
}
} == null) {
group.shutdownGracefully().addListener {
state = QuiclimeHandlerState.STOPPED
}
}
} == null) {
if (datagramChannel?.close()?.addListener {
group.shutdownGracefully().addListener {
state = QuiclimeHandlerState.STOPPED
}
} == null) {
group.shutdownGracefully().addListener {
state = QuiclimeHandlerState.STOPPED
}
}
}
}
}

View file

@ -1,14 +1,14 @@
{
"required": true,
"package": "vg.skye.e4mc_minecraft.mixins",
"compatibilityLevel": "JAVA_16",
"injectors": {
"defaultRequire": 1
},
"mixins": [
"ServerNetworkIoMixin",
"ClientConnectionMixin"
],
"client": [],
"server": []
"required": true,
"package": "vg.skye.e4mc_minecraft.mixins",
"compatibilityLevel": "JAVA_16",
"injectors": {
"defaultRequire": 1
},
"mixins": [
"ClientConnectionMixin",
"ServerNetworkIoMixin"
],
"client": [],
"server": []
}

View file

@ -31,7 +31,7 @@
"fabric": "*",
"fabricloader": ">=0.14.9",
"fabric-language-kotlin": "*",
"minecraft": "${mc_version == "1.19.4" ? ">=1.19" : ">=1.17 <1.19"}",
"minecraft": "${mc_version == "1.19.4" ? ">=1.19" : ">=1.18 <1.19"}",
"java": ">=16"
}
}

View file

@ -1,7 +1,6 @@
import com.modrinth.minotaur.dependencies.DependencyType
import com.modrinth.minotaur.dependencies.ModDependency
import org.gradle.configurationcache.extensions.capitalized
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import xyz.deftu.gradle.tools.minecraft.CurseRelation
import xyz.deftu.gradle.tools.minecraft.CurseRelationType
@ -16,7 +15,10 @@ plugins {
}
val bundle by configurations.creating {
if (mcData.isFabric) {
// Fabric imposes a hard limit of 64 on mod IDs
// the autogenned mod IDs are far longer than that
// thanks, netty!
if (false /* mcData.isFabric */) {
configurations.getByName("include").extendsFrom(this)
} else configurations.getByName("shade").extendsFrom(this)
}
@ -106,7 +108,24 @@ dependencies {
} else if (mcData.isForge) {
implementation("thedarkcolour:kotlinforforge:3.8.0")
}
bundle(implementation("org.java-websocket:Java-WebSocket:1.5.3") {
exclude(group = "org.slf4j")
bundle(implementation("com.github.vgskye.netty-incubator-codec-quic:netty-incubator-codec-classes-quic:03cbd5c2c4") {
exclude(group = "io.netty")
})
}
// bundle(implementation("io.netty.incubator:netty-incubator-codec-classes-quic:0.0.47.Final")!!)
bundle(implementation("io.netty.incubator:netty-incubator-codec-native-quic:0.0.48.Final:linux-x86_64") {
exclude(group = "io.netty")
})
bundle(implementation("io.netty.incubator:netty-incubator-codec-native-quic:0.0.48.Final:windows-x86_64") {
exclude(group = "io.netty")
})
bundle(implementation("io.netty.incubator:netty-incubator-codec-native-quic:0.0.48.Final:osx-x86_64") {
exclude(group = "io.netty")
})
bundle(implementation("io.netty.incubator:netty-incubator-codec-native-quic:0.0.48.Final:linux-aarch_64") {
exclude(group = "io.netty")
})
bundle(implementation("io.netty.incubator:netty-incubator-codec-native-quic:0.0.48.Final:osx-aarch_64") {
exclude(group = "io.netty")
})
}